Merge pull request #1422 from zed-industries/workspace-child-focus-pane-activation

Keith Simmons created

Add on_child_focus and on_child_blur to View trait

Change summary

crates/chat_panel/src/chat_panel.rs                 |   6 
crates/collab/src/integration_tests.rs              |  61 
crates/command_palette/src/command_palette.rs       |  19 
crates/contacts_panel/src/contact_finder.rs         |   6 
crates/contacts_panel/src/contacts_panel.rs         |  12 
crates/context_menu/src/context_menu.rs             |   8 
crates/diagnostics/src/diagnostics.rs               |  10 
crates/editor/src/editor.rs                         |  15 
crates/editor/src/items.rs                          |   8 
crates/editor/src/link_go_to_definition.rs          |   5 
crates/editor/src/mouse_context_menu.rs             |   3 
crates/file_finder/src/file_finder.rs               |  10 
crates/go_to_line/src/go_to_line.rs                 |   6 
crates/gpui/grammars/context-predicate/src/parser.c | 909 +++++++-------
crates/gpui/src/app.rs                              | 642 ++++++---
crates/gpui/src/presenter.rs                        | 103 
crates/gpui/src/test.rs                             |  20 
crates/outline/src/outline.rs                       |  10 
crates/picker/src/picker.rs                         |  10 
crates/project_symbols/src/project_symbols.rs       |  10 
crates/search/src/buffer_search.rs                  |  18 
crates/search/src/project_search.rs                 |  46 
crates/terminal/src/connected_view.rs               |   5 
crates/terminal/src/terminal.rs                     |   1 
crates/terminal/src/terminal_view.rs                |  13 
crates/theme_selector/src/theme_selector.rs         |  10 
crates/workspace/src/pane.rs                        |  40 
crates/workspace/src/sidebar.rs                     |   1 
crates/workspace/src/status_bar.rs                  |   2 
crates/workspace/src/workspace.rs                   | 102 
styles/package-lock.json                            |   1 
31 files changed, 1,175 insertions(+), 937 deletions(-)

Detailed changes

crates/chat_panel/src/chat_panel.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
     elements::*,
     platform::CursorStyle,
     views::{ItemType, Select, SelectStyle},
-    AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
-    Task, View, ViewContext, ViewHandle,
+    AnyViewHandle, AppContext, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
+    Subscription, Task, View, ViewContext, ViewHandle,
 };
 use menu::Confirm;
 use postage::prelude::Stream;
@@ -397,7 +397,7 @@ impl View for ChatPanel {
         .boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         if matches!(
             *self.rpc.status().borrow(),
             client::Status::Connected { .. }

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)
     });
 
@@ -4245,7 +4246,10 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
     // Clients A and B follow each other in split panes
     workspace_a.update(cx_a, |workspace, cx| {
         workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
-        assert_ne!(*workspace.active_pane(), pane_a1);
+        let pane_a1 = pane_a1.clone();
+        cx.defer(move |workspace, _| {
+            assert_ne!(*workspace.active_pane(), pane_a1);
+        });
     });
     workspace_a
         .update(cx_a, |workspace, cx| {
@@ -4258,7 +4262,10 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
         .unwrap();
     workspace_b.update(cx_b, |workspace, cx| {
         workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
-        assert_ne!(*workspace.active_pane(), pane_b1);
+        let pane_b1 = pane_b1.clone();
+        cx.defer(move |workspace, _| {
+            assert_ne!(*workspace.active_pane(), pane_b1);
+        });
     });
     workspace_b
         .update(cx_b, |workspace, cx| {
@@ -4270,17 +4277,26 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
         .await
         .unwrap();
 
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.activate_next_pane(cx);
+    });
+    // Wait for focus effects to be fully flushed
+    workspace_a.update(cx_a, |workspace, _| {
+        assert_eq!(*workspace.active_pane(), pane_a1);
+    });
+
     workspace_a
         .update(cx_a, |workspace, cx| {
-            workspace.activate_next_pane(cx);
-            assert_eq!(*workspace.active_pane(), pane_a1);
             workspace.open_path((worktree_id, "3.txt"), true, cx)
         })
         .await
         .unwrap();
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.activate_next_pane(cx);
+    });
+
     workspace_b
         .update(cx_b, |workspace, cx| {
-            workspace.activate_next_pane(cx);
             assert_eq!(*workspace.active_pane(), pane_b1);
             workspace.open_path((worktree_id, "4.txt"), true, cx)
         })
@@ -4310,17 +4326,24 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
             Some((worktree_id, "3.txt").into())
         );
         workspace.activate_next_pane(cx);
+    });
+
+    workspace_a.update(cx_a, |workspace, cx| {
         assert_eq!(
             workspace.active_item(cx).unwrap().project_path(cx),
             Some((worktree_id, "4.txt").into())
         );
     });
+
     workspace_b.update(cx_b, |workspace, cx| {
         assert_eq!(
             workspace.active_item(cx).unwrap().project_path(cx),
             Some((worktree_id, "4.txt").into())
         );
         workspace.activate_next_pane(cx);
+    });
+
+    workspace_b.update(cx_b, |workspace, cx| {
         assert_eq!(
             workspace.active_item(cx).unwrap().project_path(cx),
             Some((worktree_id, "3.txt").into())
@@ -5387,8 +5410,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 +5924,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 🔗

@@ -4,7 +4,8 @@ use gpui::{
     actions,
     elements::{ChildView, Flex, Label, ParentElement},
     keymap::Keystroke,
-    Action, Element, Entity, MouseState, MutableAppContext, View, ViewContext, ViewHandle,
+    Action, AnyViewHandle, Element, Entity, MouseState, MutableAppContext, View, ViewContext,
+    ViewHandle,
 };
 use picker::{Picker, PickerDelegate};
 use settings::Settings;
@@ -85,7 +86,7 @@ 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));
+            let this = cx.add_view(workspace.clone(), |cx| Self::new(focused_view_id, cx));
             workspace.update(cx, |workspace, cx| {
                 workspace.toggle_modal(cx, |_, cx| {
                     cx.subscribe(&this, Self::on_event).detach();
@@ -110,10 +111,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))
             }
         }
     }
@@ -132,8 +133,10 @@ impl View for CommandPalette {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.picker);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.picker);
+        }
     }
 }
 
@@ -345,8 +348,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/contact_finder.rs 🔗

@@ -1,7 +1,7 @@
 use client::{ContactRequestStatus, User, UserStore};
 use gpui::{
-    actions, elements::*, Entity, ModelHandle, MouseState, MutableAppContext, RenderContext, Task,
-    View, ViewContext, ViewHandle,
+    actions, elements::*, AnyViewHandle, Entity, ModelHandle, MouseState, MutableAppContext,
+    RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use picker::{Picker, PickerDelegate};
 use settings::Settings;
@@ -42,7 +42,7 @@ impl View for ContactFinder {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         cx.focus(&self.picker);
     }
 }

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -13,9 +13,9 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f},
     impl_actions, impl_internal_actions,
     platform::CursorStyle,
-    AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
-    MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle, WeakModelHandle,
-    WeakViewHandle,
+    AnyViewHandle, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
+    MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle,
+    WeakModelHandle, WeakViewHandle,
 };
 use join_project_notification::JoinProjectNotification;
 use menu::{Confirm, SelectNext, SelectPrev};
@@ -1152,7 +1152,7 @@ impl View for ContactsPanel {
         .boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         cx.focus(&self.filter_editor);
     }
 
@@ -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 🔗

@@ -1,6 +1,6 @@
 use gpui::{
     elements::*, geometry::vector::Vector2F, impl_internal_actions, keymap, platform::CursorStyle,
-    Action, AppContext, Axis, Entity, MouseButton, MutableAppContext, RenderContext,
+    Action, AnyViewHandle, AppContext, Axis, Entity, MouseButton, MutableAppContext, RenderContext,
     SizeConstraint, Subscription, View, ViewContext,
 };
 use menu::*;
@@ -106,7 +106,7 @@ impl View for ContextMenu {
             .boxed()
     }
 
-    fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         self.reset(cx);
     }
 }
@@ -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 🔗

@@ -99,7 +99,7 @@ impl View for ProjectDiagnosticsEditor {
         }
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         if !self.path_states.is_empty() {
             cx.focus(&self.editor);
         }
@@ -568,10 +568,6 @@ impl workspace::Item for ProjectDiagnosticsEditor {
         unreachable!()
     }
 
-    fn should_activate_item_on_event(event: &Self::Event) -> bool {
-        Editor::should_activate_item_on_event(event)
-    }
-
     fn should_update_tab_on_event(event: &Event) -> bool {
         Editor::should_update_tab_on_event(event)
     }
@@ -786,7 +782,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 +869,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 🔗

@@ -29,8 +29,8 @@ use gpui::{
     geometry::vector::{vec2f, Vector2F},
     impl_actions, impl_internal_actions,
     platform::CursorStyle,
-    text_layout, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
-    ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
+    text_layout, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox,
+    Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
     ViewContext, ViewHandle, WeakViewHandle,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
@@ -1561,7 +1561,6 @@ impl Editor {
     ) {
         if !self.focused {
             cx.focus_self();
-            cx.emit(Event::Activate);
         }
 
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -1623,7 +1622,6 @@ impl Editor {
     ) {
         if !self.focused {
             cx.focus_self();
-            cx.emit(Event::Activate);
         }
 
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -5977,7 +5975,6 @@ fn compute_scroll_position(
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 pub enum Event {
-    Activate,
     BufferEdited,
     Edited,
     Reparsed,
@@ -6033,7 +6030,7 @@ impl View for Editor {
         "Editor"
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         let focused_event = EditorFocused(cx.handle());
         cx.emit_global(focused_event);
         if let Some(rename) = self.pending_rename.as_ref() {
@@ -6054,7 +6051,7 @@ impl View for Editor {
         }
     }
 
-    fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         let blurred_event = EditorBlurred(cx.handle());
         cx.emit_global(blurred_event);
         self.focused = false;
@@ -7107,10 +7104,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| {
@@ -469,10 +469,6 @@ impl Item for Editor {
         })
     }
 
-    fn should_activate_item_on_event(event: &Event) -> bool {
-        matches!(event, Event::Activate)
-    }
-
     fn should_close_item_on_event(event: &Event) -> bool {
         matches!(event, Event::Closed)
     }
@@ -8,8 +8,8 @@ use util::TryFutureExt;
 use workspace::Workspace;
 
 use crate::{
-    Anchor, DisplayPoint, Editor, EditorSnapshot, Event, GoToDefinition, GoToTypeDefinition,
-    Select, SelectPhase,
+    Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, Select,
+    SelectPhase,
 };
 
 #[derive(Clone, PartialEq)]
@@ -355,7 +355,6 @@ fn go_to_fetched_definition_of_kind(
         editor_handle.update(cx, |editor, cx| {
             if !editor.focused {
                 cx.focus_self();
-                cx.emit(Event::Activate);
             }
         });
 

crates/editor/src/mouse_context_menu.rs 🔗

@@ -2,7 +2,7 @@ use context_menu::ContextMenuItem;
 use gpui::{geometry::vector::Vector2F, impl_internal_actions, MutableAppContext, ViewContext};
 
 use crate::{
-    DisplayPoint, Editor, EditorMode, Event, FindAllReferences, GoToDefinition, GoToTypeDefinition,
+    DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
     Rename, SelectMode, ToggleCodeActions,
 };
 
@@ -25,7 +25,6 @@ pub fn deploy_context_menu(
 ) {
     if !editor.focused {
         cx.focus_self();
-        cx.emit(Event::Activate);
     }
 
     // Don't show context menu for inline editors

crates/file_finder/src/file_finder.rs 🔗

@@ -1,7 +1,7 @@
 use fuzzy::PathMatch;
 use gpui::{
-    actions, elements::*, AppContext, Entity, ModelHandle, MouseState, MutableAppContext,
-    RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
+    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use picker::{Picker, PickerDelegate};
 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -53,8 +53,10 @@ impl View for FileFinder {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.picker);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.picker);
+        }
     }
 }
 

crates/go_to_line/src/go_to_line.rs 🔗

@@ -1,7 +1,7 @@
 use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
 use gpui::{
-    actions, elements::*, geometry::vector::Vector2F, Axis, Entity, MutableAppContext,
-    RenderContext, View, ViewContext, ViewHandle,
+    actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
+    MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
 };
 use menu::{Cancel, Confirm};
 use settings::Settings;
@@ -183,7 +183,7 @@ impl View for GoToLine {
         .named("go to line")
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         cx.focus(&self.line_editor);
     }
 }

crates/gpui/grammars/context-predicate/src/parser.c 🔗

@@ -35,115 +35,132 @@ enum {
   sym_parenthesized = 16,
 };
 
-static const char * const ts_symbol_names[] = {
-  [ts_builtin_sym_end] = "end",
-  [sym_identifier] = "identifier",
-  [anon_sym_BANG] = "!",
-  [anon_sym_AMP_AMP] = "&&",
-  [anon_sym_PIPE_PIPE] = "||",
-  [anon_sym_EQ_EQ] = "==",
-  [anon_sym_BANG_EQ] = "!=",
-  [anon_sym_LPAREN] = "(",
-  [anon_sym_RPAREN] = ")",
-  [sym_source] = "source",
-  [sym__expression] = "_expression",
-  [sym_not] = "not",
-  [sym_and] = "and",
-  [sym_or] = "or",
-  [sym_equal] = "equal",
-  [sym_not_equal] = "not_equal",
-  [sym_parenthesized] = "parenthesized",
+static const char *const ts_symbol_names[] = {
+    [ts_builtin_sym_end] = "end",
+    [sym_identifier] = "identifier",
+    [anon_sym_BANG] = "!",
+    [anon_sym_AMP_AMP] = "&&",
+    [anon_sym_PIPE_PIPE] = "||",
+    [anon_sym_EQ_EQ] = "==",
+    [anon_sym_BANG_EQ] = "!=",
+    [anon_sym_LPAREN] = "(",
+    [anon_sym_RPAREN] = ")",
+    [sym_source] = "source",
+    [sym__expression] = "_expression",
+    [sym_not] = "not",
+    [sym_and] = "and",
+    [sym_or] = "or",
+    [sym_equal] = "equal",
+    [sym_not_equal] = "not_equal",
+    [sym_parenthesized] = "parenthesized",
 };
 
 static const TSSymbol ts_symbol_map[] = {
-  [ts_builtin_sym_end] = ts_builtin_sym_end,
-  [sym_identifier] = sym_identifier,
-  [anon_sym_BANG] = anon_sym_BANG,
-  [anon_sym_AMP_AMP] = anon_sym_AMP_AMP,
-  [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE,
-  [anon_sym_EQ_EQ] = anon_sym_EQ_EQ,
-  [anon_sym_BANG_EQ] = anon_sym_BANG_EQ,
-  [anon_sym_LPAREN] = anon_sym_LPAREN,
-  [anon_sym_RPAREN] = anon_sym_RPAREN,
-  [sym_source] = sym_source,
-  [sym__expression] = sym__expression,
-  [sym_not] = sym_not,
-  [sym_and] = sym_and,
-  [sym_or] = sym_or,
-  [sym_equal] = sym_equal,
-  [sym_not_equal] = sym_not_equal,
-  [sym_parenthesized] = sym_parenthesized,
+    [ts_builtin_sym_end] = ts_builtin_sym_end,
+    [sym_identifier] = sym_identifier,
+    [anon_sym_BANG] = anon_sym_BANG,
+    [anon_sym_AMP_AMP] = anon_sym_AMP_AMP,
+    [anon_sym_PIPE_PIPE] = anon_sym_PIPE_PIPE,
+    [anon_sym_EQ_EQ] = anon_sym_EQ_EQ,
+    [anon_sym_BANG_EQ] = anon_sym_BANG_EQ,
+    [anon_sym_LPAREN] = anon_sym_LPAREN,
+    [anon_sym_RPAREN] = anon_sym_RPAREN,
+    [sym_source] = sym_source,
+    [sym__expression] = sym__expression,
+    [sym_not] = sym_not,
+    [sym_and] = sym_and,
+    [sym_or] = sym_or,
+    [sym_equal] = sym_equal,
+    [sym_not_equal] = sym_not_equal,
+    [sym_parenthesized] = sym_parenthesized,
 };
 
 static const TSSymbolMetadata ts_symbol_metadata[] = {
-  [ts_builtin_sym_end] = {
-    .visible = false,
-    .named = true,
-  },
-  [sym_identifier] = {
-    .visible = true,
-    .named = true,
-  },
-  [anon_sym_BANG] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_AMP_AMP] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_PIPE_PIPE] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_EQ_EQ] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_BANG_EQ] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_LPAREN] = {
-    .visible = true,
-    .named = false,
-  },
-  [anon_sym_RPAREN] = {
-    .visible = true,
-    .named = false,
-  },
-  [sym_source] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym__expression] = {
-    .visible = false,
-    .named = true,
-  },
-  [sym_not] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym_and] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym_or] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym_equal] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym_not_equal] = {
-    .visible = true,
-    .named = true,
-  },
-  [sym_parenthesized] = {
-    .visible = true,
-    .named = true,
-  },
+    [ts_builtin_sym_end] =
+        {
+            .visible = false,
+            .named = true,
+        },
+    [sym_identifier] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [anon_sym_BANG] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_AMP_AMP] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_PIPE_PIPE] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_EQ_EQ] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_BANG_EQ] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_LPAREN] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [anon_sym_RPAREN] =
+        {
+            .visible = true,
+            .named = false,
+        },
+    [sym_source] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym__expression] =
+        {
+            .visible = false,
+            .named = true,
+        },
+    [sym_not] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym_and] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym_or] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym_equal] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym_not_equal] =
+        {
+            .visible = true,
+            .named = true,
+        },
+    [sym_parenthesized] =
+        {
+            .visible = true,
+            .named = true,
+        },
 };
 
 enum {
@@ -152,340 +169,378 @@ enum {
   field_right = 3,
 };
 
-static const char * const ts_field_names[] = {
-  [0] = NULL,
-  [field_expression] = "expression",
-  [field_left] = "left",
-  [field_right] = "right",
+static const char *const ts_field_names[] = {
+    [0] = NULL,
+    [field_expression] = "expression",
+    [field_left] = "left",
+    [field_right] = "right",
 };
 
 static const TSFieldMapSlice ts_field_map_slices[PRODUCTION_ID_COUNT] = {
-  [1] = {.index = 0, .length = 1},
-  [2] = {.index = 1, .length = 2},
+    [1] = {.index = 0, .length = 1},
+    [2] = {.index = 1, .length = 2},
 };
 
 static const TSFieldMapEntry ts_field_map_entries[] = {
-  [0] =
-    {field_expression, 1},
-  [1] =
-    {field_left, 0},
+    [0] = {field_expression, 1},
+    [1] = {field_left, 0},
     {field_right, 2},
 };
 
-static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT][MAX_ALIAS_SEQUENCE_LENGTH] = {
-  [0] = {0},
+static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT]
+                                        [MAX_ALIAS_SEQUENCE_LENGTH] = {
+                                            [0] = {0},
 };
 
 static const uint16_t ts_non_terminal_alias_map[] = {
-  0,
+    0,
 };
 
 static bool ts_lex(TSLexer *lexer, TSStateId state) {
   START_LEXER();
   eof = lexer->eof(lexer);
   switch (state) {
-    case 0:
-      if (eof) ADVANCE(7);
-      if (lookahead == '!') ADVANCE(10);
-      if (lookahead == '&') ADVANCE(2);
-      if (lookahead == '(') ADVANCE(15);
-      if (lookahead == ')') ADVANCE(16);
-      if (lookahead == '=') ADVANCE(4);
-      if (lookahead == '|') ADVANCE(5);
-      if (lookahead == '\t' ||
-          lookahead == '\n' ||
-          lookahead == '\r' ||
-          lookahead == ' ') SKIP(0)
-      if (lookahead == '-' ||
-          ('0' <= lookahead && lookahead <= '9') ||
-          ('A' <= lookahead && lookahead <= 'Z') ||
-          lookahead == '_' ||
-          ('a' <= lookahead && lookahead <= 'z')) ADVANCE(8);
-      END_STATE();
-    case 1:
-      if (lookahead == '!') ADVANCE(9);
-      if (lookahead == '(') ADVANCE(15);
-      if (lookahead == '\t' ||
-          lookahead == '\n' ||
-          lookahead == '\r' ||
-          lookahead == ' ') SKIP(1)
-      if (lookahead == '-' ||
-          ('0' <= lookahead && lookahead <= '9') ||
-          ('A' <= lookahead && lookahead <= 'Z') ||
-          lookahead == '_' ||
-          ('a' <= lookahead && lookahead <= 'z')) ADVANCE(8);
-      END_STATE();
-    case 2:
-      if (lookahead == '&') ADVANCE(11);
-      END_STATE();
-    case 3:
-      if (lookahead == '=') ADVANCE(14);
-      END_STATE();
-    case 4:
-      if (lookahead == '=') ADVANCE(13);
-      END_STATE();
-    case 5:
-      if (lookahead == '|') ADVANCE(12);
-      END_STATE();
-    case 6:
-      if (eof) ADVANCE(7);
-      if (lookahead == '!') ADVANCE(3);
-      if (lookahead == '&') ADVANCE(2);
-      if (lookahead == ')') ADVANCE(16);
-      if (lookahead == '=') ADVANCE(4);
-      if (lookahead == '|') ADVANCE(5);
-      if (lookahead == '\t' ||
-          lookahead == '\n' ||
-          lookahead == '\r' ||
-          lookahead == ' ') SKIP(6)
-      END_STATE();
-    case 7:
-      ACCEPT_TOKEN(ts_builtin_sym_end);
-      END_STATE();
-    case 8:
-      ACCEPT_TOKEN(sym_identifier);
-      if (lookahead == '-' ||
-          ('0' <= lookahead && lookahead <= '9') ||
-          ('A' <= lookahead && lookahead <= 'Z') ||
-          lookahead == '_' ||
-          ('a' <= lookahead && lookahead <= 'z')) ADVANCE(8);
-      END_STATE();
-    case 9:
-      ACCEPT_TOKEN(anon_sym_BANG);
-      END_STATE();
-    case 10:
-      ACCEPT_TOKEN(anon_sym_BANG);
-      if (lookahead == '=') ADVANCE(14);
-      END_STATE();
-    case 11:
-      ACCEPT_TOKEN(anon_sym_AMP_AMP);
-      END_STATE();
-    case 12:
-      ACCEPT_TOKEN(anon_sym_PIPE_PIPE);
-      END_STATE();
-    case 13:
-      ACCEPT_TOKEN(anon_sym_EQ_EQ);
-      END_STATE();
-    case 14:
-      ACCEPT_TOKEN(anon_sym_BANG_EQ);
-      END_STATE();
-    case 15:
-      ACCEPT_TOKEN(anon_sym_LPAREN);
-      END_STATE();
-    case 16:
-      ACCEPT_TOKEN(anon_sym_RPAREN);
-      END_STATE();
-    default:
-      return false;
+  case 0:
+    if (eof)
+      ADVANCE(7);
+    if (lookahead == '!')
+      ADVANCE(10);
+    if (lookahead == '&')
+      ADVANCE(2);
+    if (lookahead == '(')
+      ADVANCE(15);
+    if (lookahead == ')')
+      ADVANCE(16);
+    if (lookahead == '=')
+      ADVANCE(4);
+    if (lookahead == '|')
+      ADVANCE(5);
+    if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' ||
+        lookahead == ' ')
+      SKIP(0)
+    if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') ||
+        ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' ||
+        ('a' <= lookahead && lookahead <= 'z'))
+      ADVANCE(8);
+    END_STATE();
+  case 1:
+    if (lookahead == '!')
+      ADVANCE(9);
+    if (lookahead == '(')
+      ADVANCE(15);
+    if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' ||
+        lookahead == ' ')
+      SKIP(1)
+    if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') ||
+        ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' ||
+        ('a' <= lookahead && lookahead <= 'z'))
+      ADVANCE(8);
+    END_STATE();
+  case 2:
+    if (lookahead == '&')
+      ADVANCE(11);
+    END_STATE();
+  case 3:
+    if (lookahead == '=')
+      ADVANCE(14);
+    END_STATE();
+  case 4:
+    if (lookahead == '=')
+      ADVANCE(13);
+    END_STATE();
+  case 5:
+    if (lookahead == '|')
+      ADVANCE(12);
+    END_STATE();
+  case 6:
+    if (eof)
+      ADVANCE(7);
+    if (lookahead == '!')
+      ADVANCE(3);
+    if (lookahead == '&')
+      ADVANCE(2);
+    if (lookahead == ')')
+      ADVANCE(16);
+    if (lookahead == '=')
+      ADVANCE(4);
+    if (lookahead == '|')
+      ADVANCE(5);
+    if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r' ||
+        lookahead == ' ')
+      SKIP(6)
+    END_STATE();
+  case 7:
+    ACCEPT_TOKEN(ts_builtin_sym_end);
+    END_STATE();
+  case 8:
+    ACCEPT_TOKEN(sym_identifier);
+    if (lookahead == '-' || ('0' <= lookahead && lookahead <= '9') ||
+        ('A' <= lookahead && lookahead <= 'Z') || lookahead == '_' ||
+        ('a' <= lookahead && lookahead <= 'z'))
+      ADVANCE(8);
+    END_STATE();
+  case 9:
+    ACCEPT_TOKEN(anon_sym_BANG);
+    END_STATE();
+  case 10:
+    ACCEPT_TOKEN(anon_sym_BANG);
+    if (lookahead == '=')
+      ADVANCE(14);
+    END_STATE();
+  case 11:
+    ACCEPT_TOKEN(anon_sym_AMP_AMP);
+    END_STATE();
+  case 12:
+    ACCEPT_TOKEN(anon_sym_PIPE_PIPE);
+    END_STATE();
+  case 13:
+    ACCEPT_TOKEN(anon_sym_EQ_EQ);
+    END_STATE();
+  case 14:
+    ACCEPT_TOKEN(anon_sym_BANG_EQ);
+    END_STATE();
+  case 15:
+    ACCEPT_TOKEN(anon_sym_LPAREN);
+    END_STATE();
+  case 16:
+    ACCEPT_TOKEN(anon_sym_RPAREN);
+    END_STATE();
+  default:
+    return false;
   }
 }
 
 static const TSLexMode ts_lex_modes[STATE_COUNT] = {
-  [0] = {.lex_state = 0},
-  [1] = {.lex_state = 1},
-  [2] = {.lex_state = 1},
-  [3] = {.lex_state = 1},
-  [4] = {.lex_state = 1},
-  [5] = {.lex_state = 1},
-  [6] = {.lex_state = 6},
-  [7] = {.lex_state = 0},
-  [8] = {.lex_state = 0},
-  [9] = {.lex_state = 0},
-  [10] = {.lex_state = 0},
-  [11] = {.lex_state = 0},
-  [12] = {.lex_state = 0},
-  [13] = {.lex_state = 0},
-  [14] = {.lex_state = 0},
-  [15] = {.lex_state = 0},
-  [16] = {.lex_state = 0},
-  [17] = {.lex_state = 0},
+    [0] = {.lex_state = 0},  [1] = {.lex_state = 1},  [2] = {.lex_state = 1},
+    [3] = {.lex_state = 1},  [4] = {.lex_state = 1},  [5] = {.lex_state = 1},
+    [6] = {.lex_state = 6},  [7] = {.lex_state = 0},  [8] = {.lex_state = 0},
+    [9] = {.lex_state = 0},  [10] = {.lex_state = 0}, [11] = {.lex_state = 0},
+    [12] = {.lex_state = 0}, [13] = {.lex_state = 0}, [14] = {.lex_state = 0},
+    [15] = {.lex_state = 0}, [16] = {.lex_state = 0}, [17] = {.lex_state = 0},
 };
 
 static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
-  [0] = {
-    [ts_builtin_sym_end] = ACTIONS(1),
-    [sym_identifier] = ACTIONS(1),
-    [anon_sym_BANG] = ACTIONS(1),
-    [anon_sym_AMP_AMP] = ACTIONS(1),
-    [anon_sym_PIPE_PIPE] = ACTIONS(1),
-    [anon_sym_EQ_EQ] = ACTIONS(1),
-    [anon_sym_BANG_EQ] = ACTIONS(1),
-    [anon_sym_LPAREN] = ACTIONS(1),
-    [anon_sym_RPAREN] = ACTIONS(1),
-  },
-  [1] = {
-    [sym_source] = STATE(15),
-    [sym__expression] = STATE(13),
-    [sym_not] = STATE(13),
-    [sym_and] = STATE(13),
-    [sym_or] = STATE(13),
-    [sym_equal] = STATE(13),
-    [sym_not_equal] = STATE(13),
-    [sym_parenthesized] = STATE(13),
-    [sym_identifier] = ACTIONS(3),
-    [anon_sym_BANG] = ACTIONS(5),
-    [anon_sym_LPAREN] = ACTIONS(7),
-  },
-  [2] = {
-    [sym__expression] = STATE(7),
-    [sym_not] = STATE(7),
-    [sym_and] = STATE(7),
-    [sym_or] = STATE(7),
-    [sym_equal] = STATE(7),
-    [sym_not_equal] = STATE(7),
-    [sym_parenthesized] = STATE(7),
-    [sym_identifier] = ACTIONS(3),
-    [anon_sym_BANG] = ACTIONS(5),
-    [anon_sym_LPAREN] = ACTIONS(7),
-  },
-  [3] = {
-    [sym__expression] = STATE(14),
-    [sym_not] = STATE(14),
-    [sym_and] = STATE(14),
-    [sym_or] = STATE(14),
-    [sym_equal] = STATE(14),
-    [sym_not_equal] = STATE(14),
-    [sym_parenthesized] = STATE(14),
-    [sym_identifier] = ACTIONS(3),
-    [anon_sym_BANG] = ACTIONS(5),
-    [anon_sym_LPAREN] = ACTIONS(7),
-  },
-  [4] = {
-    [sym__expression] = STATE(11),
-    [sym_not] = STATE(11),
-    [sym_and] = STATE(11),
-    [sym_or] = STATE(11),
-    [sym_equal] = STATE(11),
-    [sym_not_equal] = STATE(11),
-    [sym_parenthesized] = STATE(11),
-    [sym_identifier] = ACTIONS(3),
-    [anon_sym_BANG] = ACTIONS(5),
-    [anon_sym_LPAREN] = ACTIONS(7),
-  },
-  [5] = {
-    [sym__expression] = STATE(12),
-    [sym_not] = STATE(12),
-    [sym_and] = STATE(12),
-    [sym_or] = STATE(12),
-    [sym_equal] = STATE(12),
-    [sym_not_equal] = STATE(12),
-    [sym_parenthesized] = STATE(12),
-    [sym_identifier] = ACTIONS(3),
-    [anon_sym_BANG] = ACTIONS(5),
-    [anon_sym_LPAREN] = ACTIONS(7),
-  },
+    [0] =
+        {
+            [ts_builtin_sym_end] = ACTIONS(1),
+            [sym_identifier] = ACTIONS(1),
+            [anon_sym_BANG] = ACTIONS(1),
+            [anon_sym_AMP_AMP] = ACTIONS(1),
+            [anon_sym_PIPE_PIPE] = ACTIONS(1),
+            [anon_sym_EQ_EQ] = ACTIONS(1),
+            [anon_sym_BANG_EQ] = ACTIONS(1),
+            [anon_sym_LPAREN] = ACTIONS(1),
+            [anon_sym_RPAREN] = ACTIONS(1),
+        },
+    [1] =
+        {
+            [sym_source] = STATE(15),
+            [sym__expression] = STATE(13),
+            [sym_not] = STATE(13),
+            [sym_and] = STATE(13),
+            [sym_or] = STATE(13),
+            [sym_equal] = STATE(13),
+            [sym_not_equal] = STATE(13),
+            [sym_parenthesized] = STATE(13),
+            [sym_identifier] = ACTIONS(3),
+            [anon_sym_BANG] = ACTIONS(5),
+            [anon_sym_LPAREN] = ACTIONS(7),
+        },
+    [2] =
+        {
+            [sym__expression] = STATE(7),
+            [sym_not] = STATE(7),
+            [sym_and] = STATE(7),
+            [sym_or] = STATE(7),
+            [sym_equal] = STATE(7),
+            [sym_not_equal] = STATE(7),
+            [sym_parenthesized] = STATE(7),
+            [sym_identifier] = ACTIONS(3),
+            [anon_sym_BANG] = ACTIONS(5),
+            [anon_sym_LPAREN] = ACTIONS(7),
+        },
+    [3] =
+        {
+            [sym__expression] = STATE(14),
+            [sym_not] = STATE(14),
+            [sym_and] = STATE(14),
+            [sym_or] = STATE(14),
+            [sym_equal] = STATE(14),
+            [sym_not_equal] = STATE(14),
+            [sym_parenthesized] = STATE(14),
+            [sym_identifier] = ACTIONS(3),
+            [anon_sym_BANG] = ACTIONS(5),
+            [anon_sym_LPAREN] = ACTIONS(7),
+        },
+    [4] =
+        {
+            [sym__expression] = STATE(11),
+            [sym_not] = STATE(11),
+            [sym_and] = STATE(11),
+            [sym_or] = STATE(11),
+            [sym_equal] = STATE(11),
+            [sym_not_equal] = STATE(11),
+            [sym_parenthesized] = STATE(11),
+            [sym_identifier] = ACTIONS(3),
+            [anon_sym_BANG] = ACTIONS(5),
+            [anon_sym_LPAREN] = ACTIONS(7),
+        },
+    [5] =
+        {
+            [sym__expression] = STATE(12),
+            [sym_not] = STATE(12),
+            [sym_and] = STATE(12),
+            [sym_or] = STATE(12),
+            [sym_equal] = STATE(12),
+            [sym_not_equal] = STATE(12),
+            [sym_parenthesized] = STATE(12),
+            [sym_identifier] = ACTIONS(3),
+            [anon_sym_BANG] = ACTIONS(5),
+            [anon_sym_LPAREN] = ACTIONS(7),
+        },
 };
 
 static const uint16_t ts_small_parse_table[] = {
-  [0] = 3,
-    ACTIONS(11), 1,
-      anon_sym_EQ_EQ,
-    ACTIONS(13), 1,
-      anon_sym_BANG_EQ,
-    ACTIONS(9), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [13] = 1,
-    ACTIONS(15), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [20] = 1,
-    ACTIONS(17), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [27] = 1,
-    ACTIONS(19), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [34] = 1,
-    ACTIONS(21), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [41] = 1,
-    ACTIONS(23), 4,
-      ts_builtin_sym_end,
-      anon_sym_AMP_AMP,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [48] = 2,
-    ACTIONS(27), 1,
-      anon_sym_AMP_AMP,
-    ACTIONS(25), 3,
-      ts_builtin_sym_end,
-      anon_sym_PIPE_PIPE,
-      anon_sym_RPAREN,
-  [57] = 3,
-    ACTIONS(27), 1,
-      anon_sym_AMP_AMP,
-    ACTIONS(29), 1,
-      ts_builtin_sym_end,
-    ACTIONS(31), 1,
-      anon_sym_PIPE_PIPE,
-  [67] = 3,
-    ACTIONS(27), 1,
-      anon_sym_AMP_AMP,
-    ACTIONS(31), 1,
-      anon_sym_PIPE_PIPE,
-    ACTIONS(33), 1,
-      anon_sym_RPAREN,
-  [77] = 1,
-    ACTIONS(35), 1,
-      ts_builtin_sym_end,
-  [81] = 1,
-    ACTIONS(37), 1,
-      sym_identifier,
-  [85] = 1,
-    ACTIONS(39), 1,
-      sym_identifier,
+    [0] = 3,
+    ACTIONS(11),
+    1,
+    anon_sym_EQ_EQ,
+    ACTIONS(13),
+    1,
+    anon_sym_BANG_EQ,
+    ACTIONS(9),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [13] = 1,
+    ACTIONS(15),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [20] = 1,
+    ACTIONS(17),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [27] = 1,
+    ACTIONS(19),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [34] = 1,
+    ACTIONS(21),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [41] = 1,
+    ACTIONS(23),
+    4,
+    ts_builtin_sym_end,
+    anon_sym_AMP_AMP,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [48] = 2,
+    ACTIONS(27),
+    1,
+    anon_sym_AMP_AMP,
+    ACTIONS(25),
+    3,
+    ts_builtin_sym_end,
+    anon_sym_PIPE_PIPE,
+    anon_sym_RPAREN,
+    [57] = 3,
+    ACTIONS(27),
+    1,
+    anon_sym_AMP_AMP,
+    ACTIONS(29),
+    1,
+    ts_builtin_sym_end,
+    ACTIONS(31),
+    1,
+    anon_sym_PIPE_PIPE,
+    [67] = 3,
+    ACTIONS(27),
+    1,
+    anon_sym_AMP_AMP,
+    ACTIONS(31),
+    1,
+    anon_sym_PIPE_PIPE,
+    ACTIONS(33),
+    1,
+    anon_sym_RPAREN,
+    [77] = 1,
+    ACTIONS(35),
+    1,
+    ts_builtin_sym_end,
+    [81] = 1,
+    ACTIONS(37),
+    1,
+    sym_identifier,
+    [85] = 1,
+    ACTIONS(39),
+    1,
+    sym_identifier,
 };
 
 static const uint32_t ts_small_parse_table_map[] = {
-  [SMALL_STATE(6)] = 0,
-  [SMALL_STATE(7)] = 13,
-  [SMALL_STATE(8)] = 20,
-  [SMALL_STATE(9)] = 27,
-  [SMALL_STATE(10)] = 34,
-  [SMALL_STATE(11)] = 41,
-  [SMALL_STATE(12)] = 48,
-  [SMALL_STATE(13)] = 57,
-  [SMALL_STATE(14)] = 67,
-  [SMALL_STATE(15)] = 77,
-  [SMALL_STATE(16)] = 81,
-  [SMALL_STATE(17)] = 85,
+    [SMALL_STATE(6)] = 0,   [SMALL_STATE(7)] = 13,  [SMALL_STATE(8)] = 20,
+    [SMALL_STATE(9)] = 27,  [SMALL_STATE(10)] = 34, [SMALL_STATE(11)] = 41,
+    [SMALL_STATE(12)] = 48, [SMALL_STATE(13)] = 57, [SMALL_STATE(14)] = 67,
+    [SMALL_STATE(15)] = 77, [SMALL_STATE(16)] = 81, [SMALL_STATE(17)] = 85,
 };
 
 static const TSParseActionEntry ts_parse_actions[] = {
-  [0] = {.entry = {.count = 0, .reusable = false}},
-  [1] = {.entry = {.count = 1, .reusable = false}}, RECOVER(),
-  [3] = {.entry = {.count = 1, .reusable = true}}, SHIFT(6),
-  [5] = {.entry = {.count = 1, .reusable = true}}, SHIFT(2),
-  [7] = {.entry = {.count = 1, .reusable = true}}, SHIFT(3),
-  [9] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym__expression, 1),
-  [11] = {.entry = {.count = 1, .reusable = true}}, SHIFT(16),
-  [13] = {.entry = {.count = 1, .reusable = true}}, SHIFT(17),
-  [15] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_not, 2, .production_id = 1),
-  [17] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_equal, 3, .production_id = 2),
-  [19] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_not_equal, 3, .production_id = 2),
-  [21] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_parenthesized, 3, .production_id = 1),
-  [23] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_and, 3, .production_id = 2),
-  [25] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_or, 3, .production_id = 2),
-  [27] = {.entry = {.count = 1, .reusable = true}}, SHIFT(4),
-  [29] = {.entry = {.count = 1, .reusable = true}}, REDUCE(sym_source, 1),
-  [31] = {.entry = {.count = 1, .reusable = true}}, SHIFT(5),
-  [33] = {.entry = {.count = 1, .reusable = true}}, SHIFT(10),
-  [35] = {.entry = {.count = 1, .reusable = true}},  ACCEPT_INPUT(),
-  [37] = {.entry = {.count = 1, .reusable = true}}, SHIFT(8),
-  [39] = {.entry = {.count = 1, .reusable = true}}, SHIFT(9),
+    [0] = {.entry = {.count = 0, .reusable = false}},
+    [1] = {.entry = {.count = 1, .reusable = false}},
+    RECOVER(),
+    [3] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(6),
+    [5] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(2),
+    [7] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(3),
+    [9] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym__expression, 1),
+    [11] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(16),
+    [13] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(17),
+    [15] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_not, 2, .production_id = 1),
+    [17] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_equal, 3, .production_id = 2),
+    [19] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_not_equal, 3, .production_id = 2),
+    [21] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_parenthesized, 3, .production_id = 1),
+    [23] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_and, 3, .production_id = 2),
+    [25] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_or, 3, .production_id = 2),
+    [27] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(4),
+    [29] = {.entry = {.count = 1, .reusable = true}},
+    REDUCE(sym_source, 1),
+    [31] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(5),
+    [33] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(10),
+    [35] = {.entry = {.count = 1, .reusable = true}},
+    ACCEPT_INPUT(),
+    [37] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(8),
+    [39] = {.entry = {.count = 1, .reusable = true}},
+    SHIFT(9),
 };
 
 #ifdef __cplusplus
@@ -497,30 +552,30 @@ extern "C" {
 
 extern const TSLanguage *tree_sitter_context_predicate(void) {
   static const TSLanguage language = {
-    .version = LANGUAGE_VERSION,
-    .symbol_count = SYMBOL_COUNT,
-    .alias_count = ALIAS_COUNT,
-    .token_count = TOKEN_COUNT,
-    .external_token_count = EXTERNAL_TOKEN_COUNT,
-    .state_count = STATE_COUNT,
-    .large_state_count = LARGE_STATE_COUNT,
-    .production_id_count = PRODUCTION_ID_COUNT,
-    .field_count = FIELD_COUNT,
-    .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
-    .parse_table = &ts_parse_table[0][0],
-    .small_parse_table = ts_small_parse_table,
-    .small_parse_table_map = ts_small_parse_table_map,
-    .parse_actions = ts_parse_actions,
-    .symbol_names = ts_symbol_names,
-    .field_names = ts_field_names,
-    .field_map_slices = ts_field_map_slices,
-    .field_map_entries = ts_field_map_entries,
-    .symbol_metadata = ts_symbol_metadata,
-    .public_symbol_map = ts_symbol_map,
-    .alias_map = ts_non_terminal_alias_map,
-    .alias_sequences = &ts_alias_sequences[0][0],
-    .lex_modes = ts_lex_modes,
-    .lex_fn = ts_lex,
+      .version = LANGUAGE_VERSION,
+      .symbol_count = SYMBOL_COUNT,
+      .alias_count = ALIAS_COUNT,
+      .token_count = TOKEN_COUNT,
+      .external_token_count = EXTERNAL_TOKEN_COUNT,
+      .state_count = STATE_COUNT,
+      .large_state_count = LARGE_STATE_COUNT,
+      .production_id_count = PRODUCTION_ID_COUNT,
+      .field_count = FIELD_COUNT,
+      .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
+      .parse_table = &ts_parse_table[0][0],
+      .small_parse_table = ts_small_parse_table,
+      .small_parse_table_map = ts_small_parse_table_map,
+      .parse_actions = ts_parse_actions,
+      .symbol_names = ts_symbol_names,
+      .field_names = ts_field_names,
+      .field_map_slices = ts_field_map_slices,
+      .field_map_entries = ts_field_map_entries,
+      .symbol_metadata = ts_symbol_metadata,
+      .public_symbol_map = ts_symbol_map,
+      .alias_map = ts_non_terminal_alias_map,
+      .alias_sequences = &ts_alias_sequences[0][0],
+      .lex_modes = ts_lex_modes,
+      .lex_fn = ts_lex,
   };
   return &language;
 }

crates/gpui/src/app.rs 🔗

@@ -55,8 +55,8 @@ pub trait Entity: 'static {
 pub trait View: Entity + Sized {
     fn ui_name() -> &'static str;
     fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox;
-    fn on_focus(&mut self, _: &mut ViewContext<Self>) {}
-    fn on_blur(&mut self, _: &mut ViewContext<Self>) {}
+    fn on_focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
+    fn on_focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
     fn keymap_context(&self, _: &AppContext) -> keymap::Context {
         Self::default_keymap_context()
     }
@@ -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_from_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_from_effect(window_id, Some(view_id), &action);
+        }
     }
 
     pub fn dispatch_global_action<A: Action>(&self, action: A) {
@@ -485,11 +473,11 @@ 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(
                 Event::KeyDown(KeyDownEvent {
                     keystroke: keystroke.clone(),
@@ -533,32 +521,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 +766,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 +993,7 @@ impl MutableAppContext {
             cx: AppContext {
                 models: Default::default(),
                 views: Default::default(),
+                parents: Default::default(),
                 windows: Default::default(),
                 globals: Default::default(),
                 element_states: Default::default(),
@@ -1266,7 +1239,7 @@ impl MutableAppContext {
         let mut view = self
             .cx
             .views
-            .remove(&(params.window_id, params.view_id))
+            .remove(&(window_id, view_id))
             .ok_or(anyhow!("view not found"))?;
         let element = view.render(params, self);
         self.cx.views.insert((window_id, view_id), view);
@@ -1634,6 +1607,12 @@ impl MutableAppContext {
         }
     }
 
+    pub(crate) fn name_for_view(&self, window_id: usize, view_id: usize) -> Option<&str> {
+        self.views
+            .get(&(window_id, view_id))
+            .map(|view| view.ui_name())
+    }
+
     pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
         self.action_deserializers.keys().copied()
     }
@@ -1645,17 +1624,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 +1653,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,81 +1692,49 @@ 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(
+    // 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 path = self.parents(window_id, view_id).collect::<Vec<_>>();
 
-                    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::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
+                }
+            }))
     }
 
     fn actions_mut(
@@ -1836,34 +1772,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(focused_view_id) = self.focused_view_id(window_id) {
+            for view_id in self.parents(window_id, focused_view_id).collect::<Vec<_>>() {
+                let keymap_context = self
+                    .cx
+                    .views
+                    .get(&(window_id, view_id))
+                    .unwrap()
+                    .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_from_effect(
+                            window_id,
+                            Some(view_id),
+                            action.as_ref(),
+                        ) {
+                            self.keystroke_matcher.clear_pending();
+                            return true;
+                        }
                     }
                 }
             }
@@ -1917,15 +1853,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 +1890,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 {
@@ -1966,7 +1903,7 @@ impl MutableAppContext {
                     is_fullscreen: false,
                 },
             );
-            root_view.update(this, |view, cx| view.on_focus(cx));
+            root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
             this.open_platform_window(window_id, window_options);
 
             (window_id, root_view)
@@ -1979,7 +1916,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 +1948,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 +2014,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 +2064,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 +2117,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 +2280,17 @@ impl MutableAppContext {
                         Effect::RefreshWindows => {
                             refreshing = true;
                         }
+                        Effect::DispatchActionFrom {
+                            window_id,
+                            view_id,
+                            action,
+                        } => {
+                            self.handle_dispatch_action_from_effect(
+                                window_id,
+                                Some(view_id),
+                                action.as_ref(),
+                            );
+                        }
                         Effect::ActionDispatchNotification { action_id } => {
                             self.handle_action_dispatch_notification_effect(action_id)
                         }
@@ -2403,6 +2378,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 {
@@ -2508,14 +2500,16 @@ impl MutableAppContext {
             window.is_active = active;
 
             //Handle focus
-            let view_id = window.focused_view_id?;
-            if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
-                if active {
-                    view.on_focus(this, window_id, view_id);
-                } else {
-                    view.on_blur(this, window_id, view_id);
+            let focused_id = window.focused_view_id?;
+            for view_id in this.parents(window_id, focused_id).collect::<Vec<_>>() {
+                if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
+                    if active {
+                        view.on_focus_in(this, window_id, view_id, focused_id);
+                    } else {
+                        view.on_focus_out(this, window_id, view_id, focused_id);
+                    }
+                    this.cx.views.insert((window_id, view_id), view);
                 }
-                this.cx.views.insert((window_id, view_id), view);
             }
 
             let mut observations = this.window_activation_observations.clone();
@@ -2545,30 +2539,91 @@ impl MutableAppContext {
                 blurred_id
             });
 
-            if let Some(blurred_id) = blurred_id {
-                if let Some(mut blurred_view) = this.cx.views.remove(&(window_id, blurred_id)) {
-                    blurred_view.on_blur(this, window_id, blurred_id);
-                    this.cx.views.insert((window_id, blurred_id), blurred_view);
+            let blurred_parents = blurred_id
+                .map(|blurred_id| this.parents(window_id, blurred_id).collect::<Vec<_>>())
+                .unwrap_or_default();
+            let focused_parents = focused_id
+                .map(|focused_id| this.parents(window_id, focused_id).collect::<Vec<_>>())
+                .unwrap_or_default();
 
-                    let mut subscriptions = this.focus_observations.clone();
-                    subscriptions
-                        .emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this));
+            if let Some(blurred_id) = blurred_id {
+                for view_id in blurred_parents.iter().copied() {
+                    if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
+                        view.on_focus_out(this, window_id, view_id, blurred_id);
+                        this.cx.views.insert((window_id, view_id), view);
+                    }
                 }
+
+                let mut subscriptions = this.focus_observations.clone();
+                subscriptions
+                    .emit_and_cleanup(blurred_id, this, |callback, this| callback(false, this));
             }
 
             if let Some(focused_id) = focused_id {
-                if let Some(mut focused_view) = this.cx.views.remove(&(window_id, focused_id)) {
-                    focused_view.on_focus(this, window_id, focused_id);
-                    this.cx.views.insert((window_id, focused_id), focused_view);
-
-                    let mut subscriptions = this.focus_observations.clone();
-                    subscriptions
-                        .emit_and_cleanup(focused_id, this, |callback, this| callback(true, this));
+                for view_id in focused_parents {
+                    if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
+                        view.on_focus_in(this, window_id, view_id, focused_id);
+                        this.cx.views.insert((window_id, view_id), view);
+                    }
                 }
+
+                let mut subscriptions = this.focus_observations.clone();
+                subscriptions
+                    .emit_and_cleanup(focused_id, this, |callback, this| callback(true, this));
             }
         })
     }
 
+    fn handle_dispatch_action_from_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.halt_action_dispatch = false;
+                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 {
@@ -2700,7 +2755,7 @@ impl ReadView for MutableAppContext {
         if let Some(view) = self.cx.views.get(&(handle.window_id, handle.view_id)) {
             view.as_any().downcast_ref().expect("downcast is type safe")
         } else {
-            panic!("circular view reference");
+            panic!("circular view reference for type {}", type_name::<T>());
         }
     }
 }
@@ -2750,9 +2805,16 @@ impl Deref for MutableAppContext {
     }
 }
 
+#[derive(Debug)]
+pub enum ParentId {
+    View(usize),
+    Root,
+}
+
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
+    pub(crate) parents: HashMap<(usize, usize), ParentId>,
     windows: HashMap<usize, Window>,
     globals: HashMap<TypeId, Box<dyn Any>>,
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
@@ -2977,6 +3039,11 @@ pub enum Effect {
         callback: WindowFullscreenCallback,
     },
     RefreshWindows,
+    DispatchActionFrom {
+        window_id: usize,
+        view_id: usize,
+        action: Box<dyn Action>,
+    },
     ActionDispatchNotification {
         action_id: TypeId,
     },
@@ -3060,6 +3127,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)
@@ -3155,8 +3229,20 @@ pub trait AnyView {
     ) -> Option<Pin<Box<dyn 'static + Future<Output = ()>>>>;
     fn ui_name(&self) -> &'static str;
     fn render<'a>(&mut self, params: RenderParams, cx: &mut MutableAppContext) -> ElementBox;
-    fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
-    fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
+    fn on_focus_in(
+        &mut self,
+        cx: &mut MutableAppContext,
+        window_id: usize,
+        view_id: usize,
+        focused_id: usize,
+    );
+    fn on_focus_out(
+        &mut self,
+        cx: &mut MutableAppContext,
+        window_id: usize,
+        view_id: usize,
+        focused_id: usize,
+    );
     fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
     fn debug_json(&self, cx: &AppContext) -> serde_json::Value;
 
@@ -3181,6 +3267,14 @@ pub trait AnyView {
         window_id: usize,
         view_id: usize,
     );
+    fn any_handle(&self, window_id: usize, view_id: usize, cx: &AppContext) -> AnyViewHandle {
+        AnyViewHandle::new(
+            window_id,
+            view_id,
+            self.as_any().type_id(),
+            cx.ref_counts.clone(),
+        )
+    }
 }
 
 impl<T> AnyView for T
@@ -3214,14 +3308,48 @@ where
         View::render(self, &mut RenderContext::new(params, cx))
     }
 
-    fn on_focus(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
+    fn on_focus_in(
+        &mut self,
+        cx: &mut MutableAppContext,
+        window_id: usize,
+        view_id: usize,
+        focused_id: usize,
+    ) {
         let mut cx = ViewContext::new(cx, window_id, view_id);
-        View::on_focus(self, &mut cx);
+        let focused_view_handle: AnyViewHandle = if view_id == focused_id {
+            cx.handle().into()
+        } else {
+            let focused_type = cx
+                .views
+                .get(&(window_id, focused_id))
+                .unwrap()
+                .as_any()
+                .type_id();
+            AnyViewHandle::new(window_id, focused_id, focused_type, cx.ref_counts.clone())
+        };
+        View::on_focus_in(self, focused_view_handle, &mut cx);
     }
 
-    fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
+    fn on_focus_out(
+        &mut self,
+        cx: &mut MutableAppContext,
+        window_id: usize,
+        view_id: usize,
+        blurred_id: usize,
+    ) {
         let mut cx = ViewContext::new(cx, window_id, view_id);
-        View::on_blur(self, &mut cx);
+        let blurred_view_handle: AnyViewHandle = if view_id == blurred_id {
+            cx.handle().into()
+        } else {
+            let blurred_type = cx
+                .views
+                .get(&(window_id, blurred_id))
+                .unwrap()
+                .as_any()
+                .type_id();
+            AnyViewHandle::new(window_id, blurred_id, blurred_type, cx.ref_counts.clone())
+        };
+        View::on_focus_out(self, blurred_view_handle, &mut cx);
     }
 
     fn keymap_context(&self, cx: &AppContext) -> keymap::Context {
@@ -3640,7 +3768,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 +3780,23 @@ 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 reparent(&mut self, view_handle: impl Into<AnyViewHandle>) {
+        let view_handle = view_handle.into();
+        if self.window_id != view_handle.window_id {
+            panic!("Can't reparent view to a view from a different window");
+        }
+        self.cx
+            .parents
+            .remove(&(view_handle.window_id, view_handle.view_id));
+        let new_parent_id = self.view_id;
+        self.cx.parents.insert(
+            (view_handle.window_id, view_handle.view_id),
+            ParentId::View(new_parent_id),
+        );
     }
 
     pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
@@ -3658,7 +3806,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 +3952,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 +5952,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 +6128,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 +6369,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 +6545,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 +6668,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| {
@@ -6577,21 +6732,25 @@ mod tests {
                 "View"
             }
 
-            fn on_focus(&mut self, _: &mut ViewContext<Self>) {
-                self.events.lock().push(format!("{} focused", &self.name));
+            fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+                if cx.handle().id() == focused.id() {
+                    self.events.lock().push(format!("{} focused", &self.name));
+                }
             }
 
-            fn on_blur(&mut self, _: &mut ViewContext<Self>) {
-                self.events.lock().push(format!("{} blurred", &self.name));
+            fn on_focus_out(&mut self, blurred: AnyViewHandle, cx: &mut ViewContext<Self>) {
+                if cx.handle().id() == blurred.id() {
+                    self.events.lock().push(format!("{} blurred", &self.name));
+                }
             }
         }
 
         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 +6972,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 +6979,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_from_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 +7007,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(), |_| 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 });
+
         actions.borrow_mut().clear();
-        cx.dispatch_action(
+        cx.handle_dispatch_action_from_effect(
             window_id,
-            vec![view_2.id(), view_3.id(), view_4.id()],
+            Some(view_4.id()),
             &Action("bar".to_string()),
         );
 

crates/gpui/src/presenter.rs 🔗

@@ -10,8 +10,8 @@ use crate::{
     text_layout::TextLayoutCache,
     Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
     FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
-    ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle,
-    View, ViewHandle, WeakModelHandle, WeakViewHandle,
+    ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle,
+    UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
 };
 use collections::{HashMap, HashSet};
 use pathfinder_geometry::vector::{vec2f, Vector2F};
@@ -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,43 @@ 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);
+        let print_error = |view_id| {
+            format!(
+                "{} with id {}",
+                self.app.name_for_view(self.window_id, view_id).unwrap(),
+                view_id,
+            )
+        };
+        match (
+            self.view_stack.last(),
+            self.app.parents.get(&(self.window_id, view_id)),
+        ) {
+            (Some(layout_parent), Some(ParentId::View(app_parent))) => {
+                if layout_parent != app_parent {
+                    panic!(
+                        "View {} was laid out with parent {} when it was constructed with parent {}", 
+                        print_error(view_id), 
+                        print_error(*layout_parent),
+                        print_error(*app_parent))
+                }
+            }
+            (None, Some(ParentId::View(app_parent))) => panic!(
+                "View {} was laid out without a parent when it was constructed with parent {}",
+                print_error(view_id), 
+                print_error(*app_parent)
+            ),
+            (Some(layout_parent), Some(ParentId::Root)) => panic!(
+                "View {} was laid out with parent {} when it was constructed as a window root",
+                print_error(view_id), 
+                print_error(*layout_parent),
+            ),
+            (_, None) => panic!(
+                "View {} did not have a registered parent in the app context",
+                print_error(view_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 +647,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 +701,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/outline/src/outline.rs 🔗

@@ -4,8 +4,8 @@ use editor::{
 };
 use fuzzy::StringMatch;
 use gpui::{
-    actions, elements::*, geometry::vector::Vector2F, AppContext, Entity, MouseState,
-    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity,
+    MouseState, MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use language::Outline;
 use ordered_float::OrderedFloat;
@@ -52,8 +52,10 @@ impl View for OutlineView {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.picker);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.picker);
+        }
     }
 }
 

crates/picker/src/picker.rs 🔗

@@ -7,8 +7,8 @@ use gpui::{
     geometry::vector::{vec2f, Vector2F},
     keymap,
     platform::CursorStyle,
-    AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState, MutableAppContext,
-    RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    AnyViewHandle, AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState,
+    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
 use settings::Settings;
@@ -118,8 +118,10 @@ impl<D: PickerDelegate> View for Picker<D> {
         cx
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.query_editor);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.query_editor);
+        }
     }
 }
 

crates/project_symbols/src/project_symbols.rs 🔗

@@ -3,8 +3,8 @@ use editor::{
 };
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, elements::*, AppContext, Entity, ModelHandle, MouseState, MutableAppContext,
-    RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
+    MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle,
 };
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate};
@@ -51,8 +51,10 @@ impl View for ProjectSymbolsView {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.picker);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.picker);
+        }
     }
 }
 

crates/search/src/buffer_search.rs 🔗

@@ -6,8 +6,8 @@ use crate::{
 use collections::HashMap;
 use editor::{Anchor, Autoscroll, Editor};
 use gpui::{
-    actions, elements::*, impl_actions, platform::CursorStyle, Action, AppContext, Entity,
-    MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
+    actions, elements::*, impl_actions, platform::CursorStyle, Action, AnyViewHandle, AppContext,
+    Entity, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext,
     ViewHandle, WeakViewHandle,
 };
 use language::OffsetRangeExt;
@@ -80,8 +80,10 @@ impl View for BufferSearchBar {
         "BufferSearchBar"
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.query_editor);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.query_editor);
+        }
     }
 
     fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
@@ -600,7 +602,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 +631,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 🔗

@@ -6,9 +6,9 @@ use crate::{
 use collections::HashMap;
 use editor::{Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN};
 use gpui::{
-    actions, elements::*, platform::CursorStyle, Action, AppContext, ElementBox, Entity,
-    ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task,
-    View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
+    actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox,
+    Entity, ModelContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
+    Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
 };
 use menu::Confirm;
 use project::{search::SearchQuery, Project};
@@ -73,7 +73,6 @@ pub struct ProjectSearchView {
     regex: bool,
     query_contains_error: bool,
     active_match_index: Option<usize>,
-    results_editor_was_focused: bool,
 }
 
 pub struct ProjectSearchBar {
@@ -190,19 +189,13 @@ impl View for ProjectSearchView {
         }
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         let handle = cx.weak_handle();
         cx.update_global(|state: &mut ActiveSearches, cx| {
             state
                 .0
                 .insert(self.model.read(cx).project.downgrade(), handle)
         });
-
-        if self.results_editor_was_focused && !self.model.read(cx).match_ranges.is_empty() {
-            self.focus_results_editor(cx);
-        } else {
-            cx.focus(&self.query_editor);
-        }
     }
 }
 
@@ -330,14 +323,6 @@ impl Item for ProjectSearchView {
             .update(cx, |editor, cx| editor.navigate(data, cx))
     }
 
-    fn should_activate_item_on_event(event: &Self::Event) -> bool {
-        if let ViewEvent::EditorEvent(editor_event) = event {
-            Editor::should_activate_item_on_event(editor_event)
-        } else {
-            false
-        }
-    }
-
     fn should_update_tab_on_event(event: &ViewEvent) -> bool {
         matches!(event, ViewEvent::UpdateTab)
     }
@@ -385,12 +370,6 @@ impl ProjectSearchView {
             cx.emit(ViewEvent::EditorEvent(event.clone()))
         })
         .detach();
-        cx.observe_focus(&query_editor, |this, _, focused, _| {
-            if focused {
-                this.results_editor_was_focused = false;
-            }
-        })
-        .detach();
 
         let results_editor = cx.add_view(|cx| {
             let mut editor = Editor::for_multibuffer(excerpts, Some(project), cx);
@@ -399,12 +378,7 @@ impl ProjectSearchView {
         });
         cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
             .detach();
-        cx.observe_focus(&results_editor, |this, _, focused, _| {
-            if focused {
-                this.results_editor_was_focused = true;
-            }
-        })
-        .detach();
+
         cx.subscribe(&results_editor, |this, _, event, cx| {
             if matches!(event, editor::Event::SelectionsChanged { .. }) {
                 this.update_match_index(cx);
@@ -423,7 +397,6 @@ impl ProjectSearchView {
             regex,
             query_contains_error: false,
             active_match_index: None,
-            results_editor_was_focused: false,
         };
         this.model_changed(false, cx);
         this
@@ -905,6 +878,8 @@ impl ToolbarItemView for ProjectSearchBar {
         self.subscription = None;
         self.active_project_search = None;
         if let Some(search) = active_pane_item.and_then(|i| i.downcast::<ProjectSearchView>()) {
+            let query_editor = search.read(cx).query_editor.clone();
+            cx.reparent(query_editor);
             self.subscription = Some(cx.observe(&search, |_, _, cx| cx.notify()));
             self.active_project_search = Some(search);
             ToolbarItemLocation::PrimaryLeft {
@@ -933,7 +908,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 +925,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/terminal/src/connected_view.rs 🔗

@@ -6,7 +6,8 @@ use gpui::{
     geometry::vector::Vector2F,
     impl_internal_actions,
     keymap::Keystroke,
-    AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
+    AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View,
+    ViewContext, ViewHandle,
 };
 use workspace::pane;
 
@@ -190,7 +191,7 @@ impl View for ConnectedView {
             .boxed()
     }
 
-    fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
+    fn on_focus_in(&mut self, _: AnyViewHandle, _cx: &mut ViewContext<Self>) {
         self.has_new_content = false;
     }
 

crates/terminal/src/terminal_view.rs 🔗

@@ -152,11 +152,10 @@ impl View for TerminalView {
         }
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Activate);
-        cx.defer(|view, cx| {
-            cx.focus(view.content.handle());
-        });
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(self.content.handle());
+        }
     }
 
     fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context {
@@ -314,10 +313,6 @@ impl Item for TerminalView {
     fn should_close_item_on_event(event: &Self::Event) -> bool {
         matches!(event, &Event::CloseTerminal)
     }
-
-    fn should_activate_item_on_event(event: &Self::Event) -> bool {
-        matches!(event, &Event::Activate)
-    }
 }
 
 ///Get's the working directory for the given workspace, respecting the user's settings.

crates/theme_selector/src/theme_selector.rs 🔗

@@ -1,7 +1,7 @@
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, elements::*, AppContext, Element, ElementBox, Entity, MouseState, MutableAppContext,
-    RenderContext, View, ViewContext, ViewHandle,
+    actions, elements::*, AnyViewHandle, AppContext, Element, ElementBox, Entity, MouseState,
+    MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
 };
 use picker::{Picker, PickerDelegate};
 use settings::Settings;
@@ -249,7 +249,9 @@ impl View for ThemeSelector {
         ChildView::new(self.picker.clone()).boxed()
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.picker);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.picker);
+        }
     }
 }

crates/workspace/src/pane.rs 🔗

@@ -13,9 +13,9 @@ use gpui::{
     },
     impl_actions, impl_internal_actions,
     platform::{CursorStyle, NavigationDirection},
-    AppContext, AsyncAppContext, Entity, EventContext, ModelHandle, MouseButton, MouseButtonEvent,
-    MutableAppContext, PromptLevel, Quad, RenderContext, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+    ModelHandle, MouseButton, MouseButtonEvent, MutableAppContext, PromptLevel, Quad,
+    RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -132,7 +132,7 @@ pub fn init(cx: &mut MutableAppContext) {
 }
 
 pub enum Event {
-    Activate,
+    Focused,
     ActivateItem { local: bool },
     Remove,
     RemoveItem,
@@ -144,6 +144,7 @@ pub struct Pane {
     items: Vec<Box<dyn ItemHandle>>,
     is_active: bool,
     active_item_index: usize,
+    last_focused_view: Option<AnyWeakViewHandle>,
     autoscroll: bool,
     nav_history: Rc<RefCell<NavHistory>>,
     toolbar: ViewHandle<Toolbar>,
@@ -193,6 +194,7 @@ impl Pane {
             items: Vec::new(),
             is_active: true,
             active_item_index: 0,
+            last_focused_view: None,
             autoscroll: false,
             nav_history: Rc::new(RefCell::new(NavHistory {
                 mode: NavigationMode::Normal,
@@ -219,10 +221,6 @@ impl Pane {
         }
     }
 
-    pub fn activate(&self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Activate);
-    }
-
     pub fn go_back(
         workspace: &mut Workspace,
         pane: Option<ViewHandle<Pane>>,
@@ -287,7 +285,7 @@ impl Pane {
         mode: NavigationMode,
         cx: &mut ViewContext<Workspace>,
     ) -> Task<()> {
-        workspace.activate_pane(pane.clone(), cx);
+        cx.focus(pane.clone());
 
         let to_load = pane.update(cx, |pane, cx| {
             loop {
@@ -386,7 +384,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 +401,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
         }
@@ -441,6 +439,7 @@ impl Pane {
                 pane.active_item_index = usize::MAX;
             };
 
+            cx.reparent(&item);
             pane.items.insert(item_ix, item);
             pane.activate_item(item_ix, activate_pane, focus_item, false, cx);
             cx.notify();
@@ -522,7 +521,7 @@ impl Pane {
                 self.focus_active_item(cx);
             }
             if activate_pane {
-                self.activate(cx);
+                cx.emit(Event::Focused);
             }
             self.autoscroll = true;
             cx.notify();
@@ -1209,8 +1208,21 @@ impl View for Pane {
             .named("pane")
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        self.focus_active_item(cx);
+    fn on_focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            if let Some(last_focused_view) = self
+                .last_focused_view
+                .as_ref()
+                .and_then(|handle| handle.upgrade(cx))
+            {
+                cx.focus(last_focused_view);
+            } else {
+                self.focus_active_item(cx);
+            }
+        } else {
+            self.last_focused_view = Some(focused.downgrade());
+        }
+        cx.emit(Event::Focused);
     }
 }
 

crates/workspace/src/status_bar.rs 🔗

@@ -81,6 +81,7 @@ impl StatusBar {
     where
         T: 'static + StatusItemView,
     {
+        cx.reparent(&item);
         self.left_items.push(Box::new(item));
         cx.notify();
     }
@@ -89,6 +90,7 @@ impl StatusBar {
     where
         T: 'static + StatusItemView,
     {
+        cx.reparent(&item);
         self.right_items.push(Box::new(item));
         cx.notify();
     }

crates/workspace/src/workspace.rs 🔗

@@ -1,3 +1,8 @@
+/// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in
+/// which the workspace uses to change the activated pane.
+///
+/// This may cause issues when you're trying to write tests that use workspace focus to add items at
+/// specific locations.
 pub mod pane;
 pub mod pane_group;
 pub mod sidebar;
@@ -59,7 +64,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 +224,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)))
         });
     });
 }
@@ -297,9 +302,6 @@ pub trait Item: View {
         project: ModelHandle<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>>;
-    fn should_activate_item_on_event(_: &Self::Event) -> bool {
-        false
-    }
     fn should_close_item_on_event(_: &Self::Event) -> bool {
         false
     }
@@ -577,15 +579,6 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                 return;
             }
 
-            if T::should_activate_item_on_event(event) {
-                pane.update(cx, |pane, cx| {
-                    if let Some(ix) = pane.index_for_item(&item) {
-                        pane.activate_item(ix, true, true, false, cx);
-                        pane.activate(cx);
-                    }
-                });
-            }
-
             if T::should_update_tab_on_event(event) {
                 pane.update(cx, |_, cx| {
                     cx.emit(pane::Event::ChangeItemTitle);
@@ -708,6 +701,12 @@ impl Into<AnyViewHandle> for Box<dyn ItemHandle> {
     }
 }
 
+impl Into<AnyViewHandle> for &Box<dyn ItemHandle> {
+    fn into(self) -> AnyViewHandle {
+        self.to_any()
+    }
+}
+
 impl Clone for Box<dyn ItemHandle> {
     fn clone(&self) -> Box<dyn ItemHandle> {
         self.boxed_clone()
@@ -1432,7 +1431,7 @@ impl Workspace {
         })
         .detach();
         self.panes.push(pane.clone());
-        self.activate_pane(pane.clone(), cx);
+        cx.focus(pane.clone());
         cx.emit(Event::PaneAdded(pane.clone()));
         pane
     }
@@ -1475,12 +1474,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 +1488,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))
         })
     }
@@ -1528,7 +1526,6 @@ impl Workspace {
             }
         });
         if let Some((pane, ix)) = result {
-            self.activate_pane(pane.clone(), cx);
             pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, false, cx));
             true
         } else {
@@ -1539,7 +1536,7 @@ impl Workspace {
     fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
         let panes = self.center.panes();
         if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
-            self.activate_pane(pane, cx);
+            cx.focus(pane);
         } else {
             self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
         }
@@ -1555,7 +1552,7 @@ impl Workspace {
             let next_ix = (ix + 1) % panes.len();
             panes[next_ix].clone()
         };
-        self.activate_pane(next_pane, cx);
+        cx.focus(next_pane);
     }
 
     pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
@@ -1568,10 +1565,10 @@ impl Workspace {
             let prev_ix = if ix == 0 { panes.len() - 1 } else { ix - 1 };
             panes[prev_ix].clone()
         };
-        self.activate_pane(prev_pane, cx);
+        cx.focus(prev_pane);
     }
 
-    fn activate_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
         if self.active_pane != pane {
             self.active_pane
                 .update(cx, |pane, cx| pane.set_active(false, cx));
@@ -1582,7 +1579,6 @@ impl Workspace {
                 status_bar.set_active_pane(&self.active_pane, cx);
             });
             self.active_item_path_changed(cx);
-            cx.focus(&self.active_pane);
             cx.notify();
         }
 
@@ -1609,8 +1605,8 @@ impl Workspace {
                 pane::Event::Remove => {
                     self.remove_pane(pane, cx);
                 }
-                pane::Event::Activate => {
-                    self.activate_pane(pane, cx);
+                pane::Event::Focused => {
+                    self.handle_pane_focused(pane, cx);
                 }
                 pane::Event::ActivateItem { local } => {
                     if *local {
@@ -1643,7 +1639,6 @@ impl Workspace {
     ) -> Option<ViewHandle<Pane>> {
         pane.read(cx).active_item().map(|item| {
             let new_pane = self.add_pane(cx);
-            self.activate_pane(new_pane.clone(), cx);
             if let Some(clone) = item.clone_on_split(cx.as_mut()) {
                 Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
             }
@@ -1656,7 +1651,7 @@ impl Workspace {
     fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
         if self.center.remove(&pane).unwrap() {
             self.panes.retain(|p| p != &pane);
-            self.activate_pane(self.panes.last().unwrap().clone(), cx);
+            cx.focus(self.panes.last().unwrap().clone());
             self.unfollow(&pane, cx);
             self.last_leaders_by_pane.remove(&pane.downgrade());
             cx.notify();
@@ -2484,8 +2479,10 @@ impl View for Workspace {
             .named("workspace")
     }
 
-    fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
-        cx.focus(&self.active_pane);
+    fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        if cx.is_self_focused() {
+            cx.focus(&self.active_pane);
+        }
     }
 }
 
@@ -2732,7 +2729,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 +2748,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 +2763,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 +2777,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 +2817,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 +2911,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 +2950,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
@@ -3096,16 +3093,21 @@ mod tests {
             workspace
                 .split_pane(left_pane.clone(), SplitDirection::Right, cx)
                 .unwrap();
-            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
 
             left_pane
         });
 
+        //Need to cause an effect flush in order to respect new focus
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(cx.add_view(|_| item_3_4.clone())), cx);
+            cx.focus(left_pane.clone());
+        });
+
         // When closing all of the items in the left pane, we should be prompted twice:
         // once for project entry 0, and once for project entry 2. After those two
         // prompts, the task should complete.
+
         let close = workspace.update(cx, |workspace, cx| {
-            workspace.activate_pane(left_pane.clone(), cx);
             Pane::close_items(workspace, left_pane.clone(), cx, |_| true)
         });
 
@@ -3144,7 +3146,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 +3261,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

styles/package-lock.json 🔗

@@ -5,6 +5,7 @@
   "requires": true,
   "packages": {
     "": {
+      "name": "styles",
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {