Event dispatch moved to MutableAppContext. No longer dispatches from presenter. Not currently handling key presses properly

K Simmons created

Change summary

crates/collab/src/integration_tests.rs        |  29 
crates/command_palette/src/command_palette.rs |  10 
crates/contacts_panel/src/contacts_panel.rs   |   4 
crates/context_menu/src/context_menu.rs       |   4 
crates/diagnostics/src/diagnostics.rs         |   4 
crates/editor/src/editor.rs                   |   4 
crates/editor/src/items.rs                    |   4 
crates/gpui/src/app.rs                        | 523 ++++++++++++--------
crates/gpui/src/presenter.rs                  |  64 -
crates/gpui/src/test.rs                       |  20 
crates/search/src/buffer_search.rs            |   8 
crates/search/src/project_search.rs           |   7 
crates/workspace/src/pane.rs                  |   4 
crates/workspace/src/workspace.rs             |  45 
14 files changed, 393 insertions(+), 337 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -18,6 +18,7 @@ use futures::{channel::mpsc, Future, StreamExt as _};
 use gpui::{
     executor::{self, Deterministic},
     geometry::vector::vec2f,
+    test::EmptyView,
     ModelHandle, Task, TestAppContext, ViewHandle,
 };
 use language::{
@@ -67,7 +68,7 @@ async fn test_share_project(
     cx_b2: &mut TestAppContext,
 ) {
     cx_a.foreground().forbid_parking();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
+    let (_, window_b) = cx_b.add_window(|_| EmptyView);
     let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -145,7 +146,7 @@ async fn test_share_project(
         .await
         .unwrap();
 
-    let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+    let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 
     // TODO
     // // Create a selection set as client B and see that selection set as client A.
@@ -1736,8 +1737,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(window_b, |cx| {
+    let (_, window_b) = cx_b.add_window(|_| EmptyView);
+    let editor_b = cx_b.add_view(&window_b, |cx| {
         Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
     });
 
@@ -5387,8 +5388,8 @@ impl TestClient {
         project: &ModelHandle<Project>,
         cx: &mut TestAppContext,
     ) -> ViewHandle<Workspace> {
-        let (window_id, _) = cx.add_window(|_| EmptyView);
-        cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx))
+        let (_, root_view) = cx.add_window(|_| EmptyView);
+        cx.add_view(&root_view, |cx| Workspace::new(project.clone(), cx))
     }
 
     async fn simulate_host(
@@ -5901,19 +5902,3 @@ fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
         })
         .collect()
 }
-
-struct EmptyView;
-
-impl gpui::Entity for EmptyView {
-    type Event = ();
-}
-
-impl gpui::View for EmptyView {
-    fn ui_name() -> &'static str {
-        "empty view"
-    }
-
-    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
-        gpui::Element::boxed(gpui::elements::Empty::new())
-    }
-}

crates/command_palette/src/command_palette.rs 🔗

@@ -85,8 +85,8 @@ impl CommandPalette {
         let focused_view_id = cx.focused_view_id(window_id).unwrap_or(workspace.id());
 
         cx.as_mut().defer(move |cx| {
-            let this = cx.add_view(window_id, |cx| Self::new(focused_view_id, cx));
             workspace.update(cx, |workspace, cx| {
+                let this = cx.add_view(|cx| Self::new(focused_view_id, cx));
                 workspace.toggle_modal(cx, |_, cx| {
                     cx.subscribe(&this, Self::on_event).detach();
                     this
@@ -110,10 +110,10 @@ impl CommandPalette {
             } => {
                 let window_id = *window_id;
                 let focused_view_id = *focused_view_id;
-                let action = (*action).boxed_clone();
+                let action = action.boxed_clone();
                 workspace.dismiss_modal(cx);
                 cx.as_mut()
-                    .defer(move |cx| cx.dispatch_action_at(window_id, focused_view_id, &*action))
+                    .defer(move |cx| cx.dispatch_any_action_at(window_id, focused_view_id, action))
             }
         }
     }
@@ -345,8 +345,8 @@ mod tests {
         });
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
-        let editor = cx.add_view(window_id, |cx| {
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
+        let editor = cx.add_view(&workspace, |cx| {
             let mut editor = Editor::single_line(None, cx);
             editor.set_text("abc", cx);
             editor

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -1248,8 +1248,8 @@ mod tests {
             .0
             .read_with(cx, |worktree, _| worktree.id().to_proto());
 
-        let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx));
-        let panel = cx.add_view(0, |cx| {
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
+        let panel = cx.add_view(&workspace, |cx| {
             ContactsPanel::new(
                 user_store.clone(),
                 project_store.clone(),

crates/context_menu/src/context_menu.rs 🔗

@@ -156,9 +156,7 @@ impl ContextMenu {
     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
         if let Some(ix) = self.selected_index {
             if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
-                let window_id = cx.window_id();
-                let view_id = cx.view_id();
-                cx.dispatch_action_at(window_id, view_id, action.as_ref());
+                cx.dispatch_any_action(action.boxed_clone());
                 self.reset(cx);
             }
         }

crates/diagnostics/src/diagnostics.rs 🔗

@@ -786,7 +786,7 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await;
-        let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx));
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -873,7 +873,7 @@ mod tests {
         });
 
         // Open the project diagnostics view while there are already diagnostics.
-        let view = cx.add_view(0, |cx| {
+        let view = cx.add_view(&workspace, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 

crates/editor/src/editor.rs 🔗

@@ -7099,10 +7099,10 @@ mod tests {
     fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
         cx.set_global(Settings::test(cx));
         use workspace::Item;
-        let pane = cx.add_view(Default::default(), |cx| Pane::new(cx));
+        let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(cx));
         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
 
-        cx.add_window(Default::default(), |cx| {
+        cx.add_view(&pane, |cx| {
             let mut editor = build_editor(buffer.clone(), cx);
             let handle = cx.handle();
             editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));

crates/editor/src/items.rs 🔗

@@ -54,8 +54,8 @@ impl FollowableItem for Editor {
                     })
                 })
                 .unwrap_or_else(|| {
-                    cx.add_view(pane.window_id(), |cx| {
-                        Editor::for_buffer(buffer, Some(project), cx)
+                    pane.update(&mut cx, |_, cx| {
+                        cx.add_view(|cx| Editor::for_buffer(buffer, Some(project), cx))
                     })
                 });
             editor.update(&mut cx, |editor, cx| {

crates/gpui/src/app.rs 🔗

@@ -229,18 +229,12 @@ impl App {
             move |action| {
                 let mut cx = cx.borrow_mut();
                 if let Some(key_window_id) = cx.cx.platform.key_window_id() {
-                    if let Some((presenter, _)) =
-                        cx.presenters_and_platform_windows.get(&key_window_id)
-                    {
-                        let presenter = presenter.clone();
-                        let path = presenter.borrow().dispatch_path(cx.as_ref());
-                        cx.dispatch_action_any(key_window_id, &path, action);
-                    } else {
-                        cx.dispatch_global_action_any(action);
+                    if let Some(view_id) = cx.focused_view_id(key_window_id) {
+                        cx.handle_dispatch_action_any_effect(key_window_id, Some(view_id), action);
+                        return;
                     }
-                } else {
-                    cx.dispatch_global_action_any(action);
                 }
+                cx.dispatch_global_action_any(action);
             }
         }));
 
@@ -462,15 +456,9 @@ impl TestAppContext {
 
     pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
         let mut cx = self.cx.borrow_mut();
-        let dispatch_path = cx
-            .presenters_and_platform_windows
-            .get(&window_id)
-            .unwrap()
-            .0
-            .borrow()
-            .dispatch_path(cx.as_ref());
-
-        cx.dispatch_action_any(window_id, &dispatch_path, &action);
+        if let Some(view_id) = cx.focused_view_id(window_id) {
+            cx.handle_dispatch_action_any_effect(window_id, Some(view_id), &action);
+        }
     }
 
     pub fn dispatch_global_action<A: Action>(&self, action: A) {
@@ -485,9 +473,8 @@ impl TestAppContext {
                 .unwrap()
                 .0
                 .clone();
-            let dispatch_path = presenter.borrow().dispatch_path(cx.as_ref());
 
-            if cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) {
+            if cx.dispatch_keystroke(window_id, &keystroke) {
                 return true;
             }
             if presenter.borrow_mut().dispatch_event(
@@ -533,32 +520,24 @@ impl TestAppContext {
         (window_id, view)
     }
 
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.cx.borrow().window_ids().collect()
-    }
-
-    pub fn root_view<T: View>(&self, window_id: usize) -> Option<ViewHandle<T>> {
-        self.cx.borrow().root_view(window_id)
-    }
-
-    pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
+    pub fn add_view<T, F>(
+        &mut self,
+        parent_handle: impl Into<AnyViewHandle>,
+        build_view: F,
+    ) -> ViewHandle<T>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
-        self.cx.borrow_mut().add_view(window_id, build_view)
+        self.cx.borrow_mut().add_view(parent_handle, build_view)
     }
 
-    pub fn add_option_view<T, F>(
-        &mut self,
-        window_id: usize,
-        build_view: F,
-    ) -> Option<ViewHandle<T>>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> Option<T>,
-    {
-        self.cx.borrow_mut().add_option_view(window_id, build_view)
+    pub fn window_ids(&self) -> Vec<usize> {
+        self.cx.borrow().window_ids().collect()
+    }
+
+    pub fn root_view<T: View>(&self, window_id: usize) -> Option<ViewHandle<T>> {
+        self.cx.borrow().root_view(window_id)
     }
 
     pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
@@ -786,14 +765,6 @@ impl AsyncAppContext {
         self.update(|cx| cx.add_model(build_model))
     }
 
-    pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
-    {
-        self.update(|cx| cx.add_view(window_id, build_view))
-    }
-
     pub fn add_window<T, F>(
         &mut self,
         window_options: WindowOptions,
@@ -1021,6 +992,7 @@ impl MutableAppContext {
             cx: AppContext {
                 models: Default::default(),
                 views: Default::default(),
+                parents: Default::default(),
                 windows: Default::default(),
                 globals: Default::default(),
                 element_states: Default::default(),
@@ -1645,17 +1617,7 @@ impl MutableAppContext {
     ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
         let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect();
 
-        let presenter = self
-            .presenters_and_platform_windows
-            .get(&window_id)
-            .unwrap()
-            .0
-            .clone();
-        let mut dispatch_path = Vec::new();
-        presenter
-            .borrow()
-            .compute_dispatch_path_from(view_id, &mut dispatch_path);
-        for view_id in dispatch_path {
+        for view_id in self.parents(window_id, 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) {
@@ -1684,9 +1646,8 @@ impl MutableAppContext {
     pub fn is_action_available(&self, action: &dyn Action) -> bool {
         let action_type = action.as_any().type_id();
         if let Some(window_id) = self.cx.platform.key_window_id() {
-            if let Some((presenter, _)) = self.presenters_and_platform_windows.get(&window_id) {
-                let dispatch_path = presenter.borrow().dispatch_path(&self.cx);
-                for view_id in dispatch_path {
+            if let Some(focused_view_id) = self.focused_view_id(window_id) {
+                for view_id in self.parents(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) {
@@ -1724,83 +1685,76 @@ impl MutableAppContext {
         None
     }
 
-    pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: &dyn Action) {
-        let presenter = self
-            .presenters_and_platform_windows
-            .get(&window_id)
-            .unwrap()
-            .0
-            .clone();
-        let mut dispatch_path = Vec::new();
-        presenter
-            .borrow()
-            .compute_dispatch_path_from(view_id, &mut dispatch_path);
-        self.dispatch_action_any(window_id, &dispatch_path, action);
-    }
-
-    pub fn dispatch_action<A: Action>(
-        &mut self,
-        window_id: usize,
-        dispatch_path: Vec<usize>,
-        action: &A,
-    ) {
-        self.dispatch_action_any(window_id, &dispatch_path, action);
-    }
-
-    pub(crate) fn dispatch_action_any(
+    // pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: &dyn Action) {
+    //     let presenter = self
+    //         .presenters_and_platform_windows
+    //         .get(&window_id)
+    //         .unwrap()
+    //         .0
+    //         .clone();
+    //     let mut dispatch_path = Vec::new();
+    //     presenter
+    //         .borrow()
+    //         .compute_dispatch_path_from(view_id, &mut dispatch_path);
+    //     self.dispatch_action_any(window_id, &dispatch_path, action);
+    // }
+
+    // pub fn dispatch_action<A: Action>(
+    //     &mut self,
+    //     window_id: usize,
+    //     dispatch_path: Vec<usize>,
+    //     action: &A,
+    // ) {
+    //     self.dispatch_action_any(window_id, &dispatch_path, action);
+    // }
+
+    // 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,
-        path: &[usize],
-        action: &dyn Action,
+        view_id: usize,
+        mut visit: impl FnMut(usize, bool, &mut MutableAppContext) -> bool,
     ) -> bool {
-        self.update(|this| {
-            this.halt_action_dispatch = false;
-            for (capture_phase, view_id) in path
-                .iter()
-                .map(|view_id| (true, *view_id))
-                .chain(path.iter().rev().map(|view_id| (false, *view_id)))
-            {
-                if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
-                    let type_id = view.as_any().type_id();
-
-                    if let Some((name, mut handlers)) = this
-                        .actions_mut(capture_phase)
-                        .get_mut(&type_id)
-                        .and_then(|h| h.remove_entry(&action.id()))
-                    {
-                        for handler in handlers.iter_mut().rev() {
-                            this.halt_action_dispatch = true;
-                            handler(view.as_mut(), action, this, window_id, view_id);
-                            if this.halt_action_dispatch {
-                                break;
-                            }
-                        }
-                        this.actions_mut(capture_phase)
-                            .get_mut(&type_id)
-                            .unwrap()
-                            .insert(name, handlers);
-                    }
-
-                    this.cx.views.insert((window_id, view_id), view);
+        // List of view ids from the leaf to the root of the window
+        let mut path = vec![view_id];
+        let mut current_view = view_id;
+        while let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, current_view)) {
+            current_view = *parent_id;
+            path.push(current_view);
+        }
 
-                    if this.halt_action_dispatch {
-                        break;
-                    }
-                }
+        // 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;
             }
+        }
 
-            if !this.halt_action_dispatch {
-                this.halt_action_dispatch = this.dispatch_global_action_any(action);
+        // 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;
             }
+        }
 
-            this.pending_effects
-                .push_back(Effect::ActionDispatchNotification {
-                    action_id: action.id(),
-                });
-            this.halt_action_dispatch
-        })
+        true
     }
 
+    // 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 parents(&self, window_id: usize, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
+        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
+            }
+        })
+    }
     fn actions_mut(
         &mut self,
         capture_phase: bool,
@@ -1836,34 +1790,34 @@ impl MutableAppContext {
         self.keystroke_matcher.clear_bindings();
     }
 
-    pub fn dispatch_keystroke(
-        &mut self,
-        window_id: usize,
-        dispatch_path: Vec<usize>,
-        keystroke: &Keystroke,
-    ) -> bool {
-        let mut context_chain = Vec::new();
-        for view_id in &dispatch_path {
-            let view = self
-                .cx
-                .views
-                .get(&(window_id, *view_id))
-                .expect("view in responder chain does not exist");
-            context_chain.push(view.keymap_context(self.as_ref()));
-        }
-
+    pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: &Keystroke) -> bool {
         let mut pending = false;
-        for (i, cx) in context_chain.iter().enumerate().rev() {
-            match self
-                .keystroke_matcher
-                .push_keystroke(keystroke.clone(), dispatch_path[i], cx)
-            {
-                MatchResult::None => {}
-                MatchResult::Pending => pending = true,
-                MatchResult::Action(action) => {
-                    if self.dispatch_action_any(window_id, &dispatch_path[0..=i], action.as_ref()) {
-                        self.keystroke_matcher.clear_pending();
-                        return true;
+
+        if let Some(view_id) = self.focused_view_id(window_id) {
+            for view_id in self.parents(window_id, view_id).collect::<Vec<_>>() {
+                let keymap_context = self
+                    .cx
+                    .views
+                    .get(&(window_id, view_id))
+                    .expect("View passed to visit does not exist")
+                    .keymap_context(self.as_ref());
+
+                match self.keystroke_matcher.push_keystroke(
+                    keystroke.clone(),
+                    view_id,
+                    &keymap_context,
+                ) {
+                    MatchResult::None => {}
+                    MatchResult::Pending => pending = true,
+                    MatchResult::Action(action) => {
+                        if self.handle_dispatch_action_any_effect(
+                            window_id,
+                            Some(view_id),
+                            action.as_ref(),
+                        ) {
+                            self.keystroke_matcher.clear_pending();
+                            return true;
+                        }
                     }
                 }
             }
@@ -1917,15 +1871,14 @@ impl MutableAppContext {
     {
         self.update(|this| {
             let type_id = TypeId::of::<T>();
-            let mut state = this
-                .cx
-                .globals
-                .remove(&type_id)
-                .expect("no global has been added for this type");
-            let result = update(state.downcast_mut().unwrap(), this);
-            this.cx.globals.insert(type_id, state);
-            this.notify_global(type_id);
-            result
+            if let Some(mut state) = this.cx.globals.remove(&type_id) {
+                let result = update(state.downcast_mut().unwrap(), this);
+                this.cx.globals.insert(type_id, state);
+                this.notify_global(type_id);
+                result
+            } else {
+                panic!("No global added for {}", std::any::type_name::<T>());
+            }
         })
     }
 
@@ -1955,7 +1908,9 @@ impl MutableAppContext {
     {
         self.update(|this| {
             let window_id = post_inc(&mut this.next_window_id);
-            let root_view = this.add_view(window_id, build_root_view);
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
             this.cx.windows.insert(
                 window_id,
                 Window {
@@ -1979,7 +1934,9 @@ impl MutableAppContext {
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
         self.update(|this| {
-            let root_view = this.add_view(window_id, build_root_view);
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
             let window = this.cx.windows.get_mut(&window_id).unwrap();
             window.root_view = root_view.clone().into();
             window.focused_view_id = Some(root_view.id());
@@ -2009,11 +1966,7 @@ impl MutableAppContext {
                 app.update(|cx| {
                     if let Some(presenter) = presenter.upgrade() {
                         if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
-                            if cx.dispatch_keystroke(
-                                window_id,
-                                presenter.borrow().dispatch_path(cx.as_ref()),
-                                keystroke,
-                            ) {
+                            if cx.dispatch_keystroke(window_id, keystroke) {
                                 return true;
                             }
                         }
@@ -2079,18 +2032,45 @@ impl MutableAppContext {
         )
     }
 
-    pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
+    pub fn add_view<T, F>(
+        &mut self,
+        parent_handle: impl Into<AnyViewHandle>,
+        build_view: F,
+    ) -> ViewHandle<T>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
-        self.add_option_view(window_id, |cx| Some(build_view(cx)))
-            .unwrap()
+        let parent_handle = parent_handle.into();
+        self.build_and_insert_view(
+            parent_handle.window_id,
+            ParentId::View(parent_handle.view_id),
+            |cx| Some(build_view(cx)),
+        )
+        .unwrap()
     }
 
     pub fn add_option_view<T, F>(
+        &mut self,
+        parent_handle: impl Into<AnyViewHandle>,
+        build_view: F,
+    ) -> Option<ViewHandle<T>>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> Option<T>,
+    {
+        let parent_handle = parent_handle.into();
+        self.build_and_insert_view(
+            parent_handle.window_id,
+            ParentId::View(parent_handle.view_id),
+            build_view,
+        )
+    }
+
+    pub(crate) fn build_and_insert_view<T, F>(
         &mut self,
         window_id: usize,
+        parent_id: ParentId,
         build_view: F,
     ) -> Option<ViewHandle<T>>
     where
@@ -2102,6 +2082,7 @@ impl MutableAppContext {
             let mut cx = ViewContext::new(this, window_id, view_id);
             let handle = if let Some(view) = build_view(&mut cx) {
                 this.cx.views.insert((window_id, view_id), Box::new(view));
+                this.cx.parents.insert((window_id, view_id), parent_id);
                 if let Some(window) = this.cx.windows.get_mut(&window_id) {
                     window
                         .invalidation
@@ -2154,6 +2135,7 @@ impl MutableAppContext {
                         None
                     }
                 });
+                self.cx.parents.remove(&(window_id, view_id));
 
                 if let Some(view_id) = change_focus_to {
                     self.handle_focus_effect(window_id, Some(view_id));
@@ -2316,6 +2298,17 @@ impl MutableAppContext {
                         Effect::RefreshWindows => {
                             refreshing = true;
                         }
+                        Effect::DispatchActionFrom {
+                            window_id,
+                            view_id,
+                            action,
+                        } => {
+                            self.handle_dispatch_action_any_effect(
+                                window_id,
+                                Some(view_id),
+                                action.as_ref(),
+                            );
+                        }
                         Effect::ActionDispatchNotification { action_id } => {
                             self.handle_action_dispatch_notification_effect(action_id)
                         }
@@ -2403,6 +2396,23 @@ impl MutableAppContext {
         self.pending_effects.push_back(Effect::RefreshWindows);
     }
 
+    pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) {
+        self.dispatch_any_action_at(window_id, view_id, Box::new(action));
+    }
+
+    pub fn dispatch_any_action_at(
+        &mut self,
+        window_id: usize,
+        view_id: usize,
+        action: Box<dyn Action>,
+    ) {
+        self.pending_effects.push_back(Effect::DispatchActionFrom {
+            window_id,
+            view_id,
+            action,
+        });
+    }
+
     fn perform_window_refresh(&mut self) {
         let mut presenters = mem::take(&mut self.presenters_and_platform_windows);
         for (window_id, (presenter, window)) in &mut presenters {
@@ -2569,6 +2579,55 @@ impl MutableAppContext {
         })
     }
 
+    fn handle_dispatch_action_any_effect(
+        &mut self,
+        window_id: usize,
+        view_id: Option<usize>,
+        action: &dyn Action,
+    ) -> bool {
+        self.update(|this| {
+            if let Some(view_id) = view_id {
+                this.visit_dispatch_path(window_id, view_id, |view_id, capture_phase, this| {
+                    if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
+                        let type_id = view.as_any().type_id();
+
+                        if let Some((name, mut handlers)) = this
+                            .actions_mut(capture_phase)
+                            .get_mut(&type_id)
+                            .and_then(|h| h.remove_entry(&action.id()))
+                        {
+                            for handler in handlers.iter_mut().rev() {
+                                this.halt_action_dispatch = true;
+                                handler(view.as_mut(), action, this, window_id, view_id);
+                                if this.halt_action_dispatch {
+                                    break;
+                                }
+                            }
+                            this.actions_mut(capture_phase)
+                                .get_mut(&type_id)
+                                .unwrap()
+                                .insert(name, handlers);
+                        }
+
+                        this.cx.views.insert((window_id, view_id), view);
+                    }
+
+                    !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
+        })
+    }
+
     fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) {
         let mut callbacks = mem::take(&mut *self.action_dispatch_observations.lock());
         for (_, callback) in &mut callbacks {
@@ -2750,9 +2809,15 @@ impl Deref for MutableAppContext {
     }
 }
 
+pub enum ParentId {
+    View(usize),
+    Root,
+}
+
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
+    parents: HashMap<(usize, usize), ParentId>,
     windows: HashMap<usize, Window>,
     globals: HashMap<TypeId, Box<dyn Any>>,
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
@@ -2977,6 +3042,11 @@ pub enum Effect {
         callback: WindowFullscreenCallback,
     },
     RefreshWindows,
+    DispatchActionFrom {
+        window_id: usize,
+        view_id: usize,
+        action: Box<dyn Action>,
+    },
     ActionDispatchNotification {
         action_id: TypeId,
     },
@@ -3060,6 +3130,13 @@ impl Debug for Effect {
                 .field("view_id", view_id)
                 .field("subscription_id", subscription_id)
                 .finish(),
+            Effect::DispatchActionFrom {
+                window_id, view_id, ..
+            } => f
+                .debug_struct("Effect::DispatchActionFrom")
+                .field("window_id", window_id)
+                .field("view_id", view_id)
+                .finish(),
             Effect::ActionDispatchNotification { action_id, .. } => f
                 .debug_struct("Effect::ActionDispatchNotification")
                 .field("action_id", action_id)
@@ -3640,7 +3717,11 @@ impl<'a, T: View> ViewContext<'a, T> {
         S: View,
         F: FnOnce(&mut ViewContext<S>) -> S,
     {
-        self.app.add_view(self.window_id, build_view)
+        self.app
+            .build_and_insert_view(self.window_id, ParentId::View(self.view_id), |cx| {
+                Some(build_view(cx))
+            })
+            .unwrap()
     }
 
     pub fn add_option_view<S, F>(&mut self, build_view: F) -> Option<ViewHandle<S>>
@@ -3648,7 +3729,8 @@ impl<'a, T: View> ViewContext<'a, T> {
         S: View,
         F: FnOnce(&mut ViewContext<S>) -> Option<S>,
     {
-        self.app.add_option_view(self.window_id, build_view)
+        self.app
+            .build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view)
     }
 
     pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
@@ -3658,7 +3740,9 @@ impl<'a, T: View> ViewContext<'a, T> {
     {
         let window_id = self.window_id;
         self.update(|this| {
-            let root_view = this.add_view(window_id, build_root_view);
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
             let window = this.cx.windows.get_mut(&window_id).unwrap();
             window.root_view = root_view.clone().into();
             window.focused_view_id = Some(root_view.id());
@@ -3802,6 +3886,11 @@ impl<'a, T: View> ViewContext<'a, T> {
         self.app.notify_view(self.window_id, self.view_id);
     }
 
+    pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
+        self.app
+            .dispatch_any_action_at(self.window_id, self.view_id, action)
+    }
+
     pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut T, &mut ViewContext<T>)) {
         let handle = self.handle();
         self.app.defer(move |cx| {
@@ -5797,9 +5886,9 @@ mod tests {
             }
         }
 
-        let (window_id, _) = cx.add_window(Default::default(), |cx| View::new(None, cx));
-        let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx));
-        let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx));
+        let (_, root_view) = cx.add_window(Default::default(), |cx| View::new(None, cx));
+        let handle_1 = cx.add_view(&root_view, |cx| View::new(None, cx));
+        let handle_2 = cx.add_view(&root_view, |cx| View::new(Some(handle_1.clone()), cx));
         assert_eq!(cx.cx.views.len(), 3);
 
         handle_1.update(cx, |view, cx| {
@@ -5973,8 +6062,8 @@ mod tests {
             type Event = usize;
         }
 
-        let (window_id, handle_1) = cx.add_window(Default::default(), |_| View::default());
-        let handle_2 = cx.add_view(window_id, |_| View::default());
+        let (_, handle_1) = cx.add_window(Default::default(), |_| View::default());
+        let handle_2 = cx.add_view(&handle_1, |_| View::default());
         let handle_3 = cx.add_model(|_| Model);
 
         handle_1.update(cx, |_, cx| {
@@ -6214,9 +6303,9 @@ mod tests {
             type Event = ();
         }
 
-        let (window_id, _) = cx.add_window(Default::default(), |_| View);
-        let observing_view = cx.add_view(window_id, |_| View);
-        let emitting_view = cx.add_view(window_id, |_| View);
+        let (_, root_view) = cx.add_window(Default::default(), |_| View);
+        let observing_view = cx.add_view(&root_view, |_| View);
+        let emitting_view = cx.add_view(&root_view, |_| View);
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -6390,8 +6479,8 @@ mod tests {
             type Event = ();
         }
 
-        let (window_id, _) = cx.add_window(Default::default(), |_| View);
-        let observing_view = cx.add_view(window_id, |_| View);
+        let (_, root_view) = cx.add_window(Default::default(), |_| View);
+        let observing_view = cx.add_view(root_view, |_| View);
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -6513,9 +6602,9 @@ mod tests {
             }
         }
 
-        let (window_id, _) = cx.add_window(Default::default(), |_| View);
-        let observing_view = cx.add_view(window_id, |_| View);
-        let observed_view = cx.add_view(window_id, |_| View);
+        let (_, root_view) = cx.add_window(Default::default(), |_| View);
+        let observing_view = cx.add_view(&root_view, |_| View);
+        let observed_view = cx.add_view(&root_view, |_| View);
 
         let observation_count = Rc::new(RefCell::new(0));
         observing_view.update(cx, |_, cx| {
@@ -6587,11 +6676,11 @@ mod tests {
         }
 
         let view_events: Arc<Mutex<Vec<String>>> = Default::default();
-        let (window_id, view_1) = cx.add_window(Default::default(), |_| View {
+        let (_, view_1) = cx.add_window(Default::default(), |_| View {
             events: view_events.clone(),
             name: "view 1".to_string(),
         });
-        let view_2 = cx.add_view(window_id, |_| View {
+        let view_2 = cx.add_view(&view_1, |_| View {
             events: view_events.clone(),
             name: "view 2".to_string(),
         });
@@ -6813,11 +6902,6 @@ mod tests {
             }
         });
 
-        let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
-        let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 });
-        let view_3 = cx.add_view(window_id, |_| ViewA { id: 3 });
-        let view_4 = cx.add_view(window_id, |_| ViewB { id: 4 });
-
         let observed_actions = Rc::new(RefCell::new(Vec::new()));
         cx.observe_actions({
             let observed_actions = observed_actions.clone();
@@ -6825,9 +6909,14 @@ mod tests {
         })
         .detach();
 
-        cx.dispatch_action(
+        let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
+        let view_2 = cx.add_view(&view_1, |_| ViewB { id: 2 });
+        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_any_effect(
             window_id,
-            vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()],
+            Some(view_4.id()),
             &Action("bar".to_string()),
         );
 
@@ -6848,10 +6937,15 @@ mod tests {
         assert_eq!(*observed_actions.borrow(), [Action::default().id()]);
 
         // Remove view_1, which doesn't propagate the action
+
+        let (window_id, view_2) = cx.add_window(Default::default(), |_| ViewA { id: 1 });
+        let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 });
+        let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 });
+
         actions.borrow_mut().clear();
-        cx.dispatch_action(
+        cx.handle_dispatch_action_any_effect(
             window_id,
-            vec![view_2.id(), view_3.id(), view_4.id()],
+            Some(view_4.id()),
             &Action("bar".to_string()),
         );
 
@@ -6924,8 +7018,9 @@ mod tests {
         view_3.keymap_context.set.insert("c".into());
 
         let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1);
-        let view_2 = cx.add_view(window_id, |_| view_2);
-        let view_3 = cx.add_view(window_id, |_| view_3);
+        let view_2 = cx.add_view(&view_1, |_| view_2);
+        let view_3 = cx.add_view(&view_2, |_| view_3);
+        cx.focus(window_id, Some(view_3.id()));
 
         // This keymap's only binding dispatches an action on view 2 because that view will have
         // "a" and "b" in its context, but not "c".
@@ -6963,20 +7058,12 @@ mod tests {
             }
         });
 
-        cx.dispatch_keystroke(
-            window_id,
-            vec![view_1.id(), view_2.id(), view_3.id()],
-            &Keystroke::parse("a").unwrap(),
-        );
+        cx.dispatch_keystroke(window_id, &Keystroke::parse("a").unwrap());
 
         assert_eq!(&*actions.borrow(), &["2 a"]);
 
         actions.borrow_mut().clear();
-        cx.dispatch_keystroke(
-            window_id,
-            vec![view_1.id(), view_2.id(), view_3.id()],
-            &Keystroke::parse("b").unwrap(),
-        );
+        cx.dispatch_keystroke(window_id, &Keystroke::parse("b").unwrap());
 
         assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]);
     }
@@ -7130,8 +7217,8 @@ mod tests {
             }
         }
 
-        let window_id = cx.add_window(|_| View).0;
-        let view = cx.add_view(window_id, |_| View);
+        let (_, root_view) = cx.add_window(|_| View);
+        let view = cx.add_view(&root_view, |_| View);
 
         let condition = view.condition(&cx, |_, _| false);
         cx.update(|_| drop(view));
@@ -7164,7 +7251,7 @@ mod tests {
             Some("render count: 0")
         );
 
-        let view = cx.add_view(window_id, |cx| {
+        let view = cx.add_view(&root_view, |cx| {
             cx.refresh_windows();
             View(0)
         });

crates/gpui/src/presenter.rs 🔗

@@ -26,7 +26,6 @@ use std::{
 pub struct Presenter {
     window_id: usize,
     pub(crate) rendered_views: HashMap<usize, ElementBox>,
-    parents: HashMap<usize, usize>,
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
     font_cache: Arc<FontCache>,
@@ -52,7 +51,6 @@ impl Presenter {
         Self {
             window_id,
             rendered_views: cx.render_views(window_id, titlebar_height),
-            parents: Default::default(),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
             font_cache,
@@ -67,22 +65,22 @@ impl Presenter {
         }
     }
 
-    pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
-        let mut path = Vec::new();
-        if let Some(view_id) = app.focused_view_id(self.window_id) {
-            self.compute_dispatch_path_from(view_id, &mut path)
-        }
-        path
-    }
+    // pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
+    //     let mut path = Vec::new();
+    //     if let Some(view_id) = app.focused_view_id(self.window_id) {
+    //         self.compute_dispatch_path_from(view_id, &mut path)
+    //     }
+    //     path
+    // }
 
-    pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec<usize>) {
-        path.push(view_id);
-        while let Some(parent_id) = self.parents.get(&view_id).copied() {
-            path.push(parent_id);
-            view_id = parent_id;
-        }
-        path.reverse();
-    }
+    // pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec<usize>) {
+    //     path.push(view_id);
+    //     while let Some(parent_id) = self.parents.get(&view_id).copied() {
+    //         path.push(parent_id);
+    //         view_id = parent_id;
+    //     }
+    //     path.reverse();
+    // }
 
     pub fn invalidate(
         &mut self,
@@ -93,7 +91,6 @@ impl Presenter {
         for view_id in &invalidation.removed {
             invalidation.updated.remove(&view_id);
             self.rendered_views.remove(&view_id);
-            self.parents.remove(&view_id);
         }
         for view_id in &invalidation.updated {
             self.rendered_views.insert(
@@ -191,7 +188,6 @@ impl Presenter {
         LayoutContext {
             window_id: self.window_id,
             rendered_views: &mut self.rendered_views,
-            parents: &mut self.parents,
             font_cache: &self.font_cache,
             font_system: cx.platform().fonts(),
             text_layout_cache: &self.text_layout_cache,
@@ -344,21 +340,11 @@ impl Presenter {
             }
 
             invalidated_views.extend(event_cx.invalidated_views);
-            let dispatch_directives = event_cx.dispatched_actions;
 
             for view_id in invalidated_views {
                 cx.notify_view(self.window_id, view_id);
             }
 
-            let mut dispatch_path = Vec::new();
-            for directive in dispatch_directives {
-                dispatch_path.clear();
-                if let Some(view_id) = directive.dispatcher_view_id {
-                    self.compute_dispatch_path_from(view_id, &mut dispatch_path);
-                }
-                cx.dispatch_action_any(self.window_id, &dispatch_path, directive.action.as_ref());
-            }
-
             handled
         } else {
             false
@@ -372,9 +358,6 @@ impl Presenter {
         cx: &'a mut MutableAppContext,
     ) -> (bool, EventContext<'a>) {
         let mut hover_regions = Vec::new();
-        // let mut unhovered_regions = Vec::new();
-        // let mut hovered_regions = Vec::new();
-
         if let Event::MouseMoved(
             e @ MouseMovedEvent {
                 position,
@@ -446,7 +429,6 @@ impl Presenter {
     ) -> EventContext<'a> {
         EventContext {
             rendered_views: &mut self.rendered_views,
-            dispatched_actions: Default::default(),
             font_cache: &self.font_cache,
             text_layout_cache: &self.text_layout_cache,
             view_stack: Default::default(),
@@ -473,15 +455,9 @@ impl Presenter {
     }
 }
 
-pub struct DispatchDirective {
-    pub dispatcher_view_id: Option<usize>,
-    pub action: Box<dyn Action>,
-}
-
 pub struct LayoutContext<'a> {
     window_id: usize,
     rendered_views: &'a mut HashMap<usize, ElementBox>,
-    parents: &'a mut HashMap<usize, usize>,
     view_stack: Vec<usize>,
     pub font_cache: &'a Arc<FontCache>,
     pub font_system: Arc<dyn FontSystem>,
@@ -506,9 +482,6 @@ impl<'a> LayoutContext<'a> {
     }
 
     fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
-        if let Some(parent_id) = self.view_stack.last() {
-            self.parents.insert(view_id, *parent_id);
-        }
         self.view_stack.push(view_id);
         let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
         let size = rendered_view.layout(constraint, self);
@@ -637,7 +610,6 @@ impl<'a> Deref for PaintContext<'a> {
 
 pub struct EventContext<'a> {
     rendered_views: &'a mut HashMap<usize, ElementBox>,
-    dispatched_actions: Vec<DispatchDirective>,
     pub font_cache: &'a FontCache,
     pub text_layout_cache: &'a TextLayoutCache,
     pub app: &'a mut MutableAppContext,
@@ -692,10 +664,8 @@ impl<'a> EventContext<'a> {
     }
 
     pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
-        self.dispatched_actions.push(DispatchDirective {
-            dispatcher_view_id: self.view_stack.last().copied(),
-            action,
-        });
+        self.app
+            .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action)
     }
 
     pub fn dispatch_action<A: Action>(&mut self, action: A) {

crates/gpui/src/test.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    executor, platform, Entity, FontCache, Handle, LeakDetector, MutableAppContext, Platform,
-    Subscription, TestAppContext,
+    elements::Empty, executor, platform, Element, ElementBox, Entity, FontCache, Handle,
+    LeakDetector, MutableAppContext, Platform, RenderContext, Subscription, TestAppContext, View,
 };
 use futures::StreamExt;
 use parking_lot::Mutex;
@@ -162,3 +162,19 @@ where
 
     Observation { rx, _subscription }
 }
+
+pub struct EmptyView;
+
+impl Entity for EmptyView {
+    type Event = ();
+}
+
+impl View for EmptyView {
+    fn ui_name() -> &'static str {
+        "empty view"
+    }
+
+    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+        Element::boxed(Empty::new())
+    }
+}

crates/search/src/buffer_search.rs 🔗

@@ -600,7 +600,7 @@ impl BufferSearchBar {
 mod tests {
     use super::*;
     use editor::{DisplayPoint, Editor};
-    use gpui::{color::Color, TestAppContext};
+    use gpui::{color::Color, test::EmptyView, TestAppContext};
     use language::Buffer;
     use std::sync::Arc;
     use unindent::Unindent as _;
@@ -629,11 +629,13 @@ mod tests {
                 cx,
             )
         });
-        let editor = cx.add_view(Default::default(), |cx| {
+        let (_, root_view) = cx.add_window(|_| EmptyView);
+
+        let editor = cx.add_view(&root_view, |cx| {
             Editor::for_buffer(buffer.clone(), None, cx)
         });
 
-        let search_bar = cx.add_view(Default::default(), |cx| {
+        let search_bar = cx.add_view(&root_view, |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(false, true, cx);

crates/search/src/project_search.rs 🔗

@@ -933,7 +933,8 @@ mod tests {
         cx.update(|cx| {
             let mut settings = Settings::test(cx);
             settings.theme = Arc::new(theme);
-            cx.set_global(settings)
+            cx.set_global(settings);
+            cx.set_global(ActiveSearches::default());
         });
 
         let fs = FakeFs::new(cx.background());
@@ -949,9 +950,7 @@ mod tests {
         .await;
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
-        let search_view = cx.add_view(Default::default(), |cx| {
-            ProjectSearchView::new(search.clone(), cx)
-        });
+        let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx));
 
         search_view.update(cx, |search_view, cx| {
             search_view

crates/workspace/src/pane.rs 🔗

@@ -386,7 +386,7 @@ impl Pane {
         project_entry_id: ProjectEntryId,
         focus_item: bool,
         cx: &mut ViewContext<Workspace>,
-        build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
+        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
     ) -> Box<dyn ItemHandle> {
         let existing_item = pane.update(cx, |pane, cx| {
             for (ix, item) in pane.items.iter().enumerate() {
@@ -403,7 +403,7 @@ impl Pane {
         if let Some(existing_item) = existing_item {
             existing_item
         } else {
-            let item = build_item(cx);
+            let item = pane.update(cx, |_, cx| build_item(cx));
             Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx);
             item
         }

crates/workspace/src/workspace.rs 🔗

@@ -59,7 +59,7 @@ use waiting_room::WaitingRoom;
 
 type ProjectItemBuilders = HashMap<
     TypeId,
-    fn(usize, ModelHandle<Project>, AnyModelHandle, &mut MutableAppContext) -> Box<dyn ItemHandle>,
+    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 >;
 
 type FollowableItemBuilder = fn(
@@ -219,9 +219,9 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
 
 pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
     cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
-        builders.insert(TypeId::of::<I::Item>(), |window_id, project, model, cx| {
+        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
             let item = model.downcast::<I::Item>().unwrap();
-            Box::new(cx.add_view(window_id, |cx| I::for_project_item(project, item, cx)))
+            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
         });
     });
 }
@@ -1475,12 +1475,11 @@ impl Workspace {
     ) -> Task<
         Result<(
             ProjectEntryId,
-            impl 'static + FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
+            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
         )>,
     > {
         let project = self.project().clone();
         let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
-        let window_id = cx.window_id();
         cx.as_mut().spawn(|mut cx| async move {
             let (project_entry_id, project_item) = project_item.await?;
             let build_item = cx.update(|cx| {
@@ -1490,7 +1489,7 @@ impl Workspace {
                     .cloned()
             })?;
             let build_item =
-                move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx);
+                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
             Ok((project_entry_id, build_item))
         })
     }
@@ -2732,7 +2731,7 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
         (app_state.initialize_workspace)(&mut workspace, app_state, cx);
         workspace
     });
-    cx.dispatch_action(window_id, vec![workspace.id()], &NewFile);
+    cx.dispatch_action_at(window_id, workspace.id(), NewFile);
 }
 
 #[cfg(test)]
@@ -2751,10 +2750,10 @@ mod tests {
 
         let fs = FakeFs::new(cx.background());
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
 
         // Adding an item with no ambiguity renders the tab without detail.
-        let item1 = cx.add_view(window_id, |_| {
+        let item1 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
             item
@@ -2766,7 +2765,7 @@ mod tests {
 
         // Adding an item that creates ambiguity increases the level of detail on
         // both tabs.
-        let item2 = cx.add_view(window_id, |_| {
+        let item2 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -2780,7 +2779,7 @@ mod tests {
         // Adding an item that creates ambiguity increases the level of detail only
         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
         // we stop at the highest detail available.
-        let item3 = cx.add_view(window_id, |_| {
+        let item3 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -2820,12 +2819,12 @@ mod tests {
             project.worktrees(cx).next().unwrap().read(cx).id()
         });
 
-        let item1 = cx.add_view(window_id, |_| {
+        let item1 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.project_path = Some((worktree_id, "one.txt").into());
             item
         });
-        let item2 = cx.add_view(window_id, |_| {
+        let item2 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.project_path = Some((worktree_id, "two.txt").into());
             item
@@ -2914,19 +2913,19 @@ mod tests {
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
 
         // When there are no dirty items, there's nothing to do.
-        let item1 = cx.add_view(window_id, |_| TestItem::new());
+        let item1 = cx.add_view(&workspace, |_| TestItem::new());
         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
         let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx));
         assert_eq!(task.await.unwrap(), true);
 
         // When there are dirty untitled items, prompt to save each one. If the user
         // cancels any prompt, then abort.
-        let item2 = cx.add_view(window_id, |_| {
+        let item2 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item
         });
-        let item3 = cx.add_view(window_id, |_| {
+        let item3 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
@@ -2953,27 +2952,27 @@ mod tests {
         let project = Project::test(fs, None, cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
 
-        let item1 = cx.add_view(window_id, |_| {
+        let item1 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
             item
         });
-        let item2 = cx.add_view(window_id, |_| {
+        let item2 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item.has_conflict = true;
             item.project_entry_ids = vec![ProjectEntryId::from_proto(2)];
             item
         });
-        let item3 = cx.add_view(window_id, |_| {
+        let item3 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item.has_conflict = true;
             item.project_entry_ids = vec![ProjectEntryId::from_proto(3)];
             item
         });
-        let item4 = cx.add_view(window_id, |_| {
+        let item4 = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.is_dirty = true;
             item
@@ -3144,7 +3143,7 @@ mod tests {
         let project = Project::test(fs, [], cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
 
-        let item = cx.add_view(window_id, |_| {
+        let item = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
             item
@@ -3259,9 +3258,9 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
+        let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
 
-        let item = cx.add_view(window_id, |_| {
+        let item = cx.add_view(&workspace, |_| {
             let mut item = TestItem::new();
             item.project_entry_ids = vec![ProjectEntryId::from_proto(1)];
             item