From 4271eb36241b97aee562df9f1d3da44cfed5755c Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 4 Aug 2022 20:55:10 -0700 Subject: [PATCH] Event dispatch moved to MutableAppContext. No longer dispatches from presenter. Not currently handling key presses properly --- crates/collab/src/integration_tests.rs | 29 +- crates/command_palette/src/command_palette.rs | 10 +- crates/contacts_panel/src/contacts_panel.rs | 4 +- crates/context_menu/src/context_menu.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 4 +- crates/editor/src/editor.rs | 4 +- crates/editor/src/items.rs | 4 +- crates/gpui/src/app.rs | 521 ++++++++++-------- crates/gpui/src/presenter.rs | 64 +-- crates/gpui/src/test.rs | 20 +- crates/search/src/buffer_search.rs | 8 +- crates/search/src/project_search.rs | 7 +- crates/workspace/src/pane.rs | 4 +- crates/workspace/src/workspace.rs | 45 +- 14 files changed, 392 insertions(+), 336 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 2a68c2056e..4118e5963c 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -18,6 +18,7 @@ use futures::{channel::mpsc, Future, StreamExt as _}; use gpui::{ executor::{self, Deterministic}, geometry::vector::vec2f, + test::EmptyView, ModelHandle, Task, TestAppContext, ViewHandle, }; use language::{ @@ -67,7 +68,7 @@ async fn test_share_project( cx_b2: &mut TestAppContext, ) { cx_a.foreground().forbid_parking(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); + let (_, window_b) = cx_b.add_window(|_| EmptyView); let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -145,7 +146,7 @@ async fn test_share_project( .await .unwrap(); - let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx)); + let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx)); // TODO // // Create a selection set as client B and see that selection set as client A. @@ -1736,8 +1737,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let (window_b, _) = cx_b.add_window(|_| EmptyView); - let editor_b = cx_b.add_view(window_b, |cx| { + let (_, window_b) = cx_b.add_window(|_| EmptyView); + let editor_b = cx_b.add_view(&window_b, |cx| { Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) }); @@ -5387,8 +5388,8 @@ impl TestClient { project: &ModelHandle, cx: &mut TestAppContext, ) -> ViewHandle { - let (window_id, _) = cx.add_window(|_| EmptyView); - cx.add_view(window_id, |cx| Workspace::new(project.clone(), cx)) + let (_, root_view) = cx.add_window(|_| EmptyView); + cx.add_view(&root_view, |cx| Workspace::new(project.clone(), cx)) } async fn simulate_host( @@ -5901,19 +5902,3 @@ fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> { }) .collect() } - -struct EmptyView; - -impl gpui::Entity for EmptyView { - type Event = (); -} - -impl gpui::View for EmptyView { - fn ui_name() -> &'static str { - "empty view" - } - - fn render(&mut self, _: &mut gpui::RenderContext) -> gpui::ElementBox { - gpui::Element::boxed(gpui::elements::Empty::new()) - } -} diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 5f438057ee..5f213284e8 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -85,8 +85,8 @@ impl CommandPalette { let focused_view_id = cx.focused_view_id(window_id).unwrap_or(workspace.id()); cx.as_mut().defer(move |cx| { - let this = cx.add_view(window_id, |cx| Self::new(focused_view_id, cx)); workspace.update(cx, |workspace, cx| { + let this = cx.add_view(|cx| Self::new(focused_view_id, cx)); workspace.toggle_modal(cx, |_, cx| { cx.subscribe(&this, Self::on_event).detach(); this @@ -110,10 +110,10 @@ impl CommandPalette { } => { let window_id = *window_id; let focused_view_id = *focused_view_id; - let action = (*action).boxed_clone(); + let action = action.boxed_clone(); workspace.dismiss_modal(cx); cx.as_mut() - .defer(move |cx| cx.dispatch_action_at(window_id, focused_view_id, &*action)) + .defer(move |cx| cx.dispatch_any_action_at(window_id, focused_view_id, action)) } } } @@ -345,8 +345,8 @@ mod tests { }); let project = Project::test(app_state.fs.clone(), [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); - let editor = cx.add_view(window_id, |cx| { + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let editor = cx.add_view(&workspace, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); editor diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index aef9037879..4aff5b1a74 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -1248,8 +1248,8 @@ mod tests { .0 .read_with(cx, |worktree, _| worktree.id().to_proto()); - let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx)); - let panel = cx.add_view(0, |cx| { + let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let panel = cx.add_view(&workspace, |cx| { ContactsPanel::new( user_store.clone(), project_store.clone(), diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index d49f817de6..b17718577f 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -156,9 +156,7 @@ impl ContextMenu { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { 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); } } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8d5746f8fb..e9692c5493 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -786,7 +786,7 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; - let workspace = cx.add_view(0, |cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); // Create some diagnostics project.update(cx, |project, cx| { @@ -873,7 +873,7 @@ mod tests { }); // Open the project diagnostics view while there are already diagnostics. - let view = cx.add_view(0, |cx| { + let view = cx.add_view(&workspace, |cx| { ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7c2d560a41..e4ff272440 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7099,10 +7099,10 @@ mod tests { fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); use workspace::Item; - let pane = cx.add_view(Default::default(), |cx| Pane::new(cx)); + let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(cx)); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); - cx.add_window(Default::default(), |cx| { + cx.add_view(&pane, |cx| { let mut editor = build_editor(buffer.clone(), cx); let handle = cx.handle(); editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle))); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 5bb8d4d0b2..9837c5070f 100644 --- a/crates/editor/src/items.rs +++ b/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| { diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index cc4c1191f6..a1b782039a 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -229,18 +229,12 @@ impl App { move |action| { let mut cx = cx.borrow_mut(); if let Some(key_window_id) = cx.cx.platform.key_window_id() { - if let Some((presenter, _)) = - cx.presenters_and_platform_windows.get(&key_window_id) - { - let presenter = presenter.clone(); - let path = presenter.borrow().dispatch_path(cx.as_ref()); - cx.dispatch_action_any(key_window_id, &path, action); - } else { - cx.dispatch_global_action_any(action); + if let Some(view_id) = cx.focused_view_id(key_window_id) { + cx.handle_dispatch_action_any_effect(key_window_id, Some(view_id), action); + return; } - } else { - cx.dispatch_global_action_any(action); } + cx.dispatch_global_action_any(action); } })); @@ -462,15 +456,9 @@ impl TestAppContext { pub fn dispatch_action(&self, window_id: usize, action: A) { let mut cx = self.cx.borrow_mut(); - let dispatch_path = cx - .presenters_and_platform_windows - .get(&window_id) - .unwrap() - .0 - .borrow() - .dispatch_path(cx.as_ref()); - - cx.dispatch_action_any(window_id, &dispatch_path, &action); + if let Some(view_id) = cx.focused_view_id(window_id) { + cx.handle_dispatch_action_any_effect(window_id, Some(view_id), &action); + } } pub fn dispatch_global_action(&self, action: A) { @@ -485,9 +473,8 @@ impl TestAppContext { .unwrap() .0 .clone(); - let dispatch_path = presenter.borrow().dispatch_path(cx.as_ref()); - if cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) { + if cx.dispatch_keystroke(window_id, &keystroke) { return true; } if presenter.borrow_mut().dispatch_event( @@ -533,6 +520,18 @@ impl TestAppContext { (window_id, view) } + pub fn add_view( + &mut self, + parent_handle: impl Into, + build_view: F, + ) -> ViewHandle + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.cx.borrow_mut().add_view(parent_handle, build_view) + } + pub fn window_ids(&self) -> Vec { self.cx.borrow().window_ids().collect() } @@ -541,26 +540,6 @@ impl TestAppContext { self.cx.borrow().root_view(window_id) } - pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle - where - T: View, - F: FnOnce(&mut ViewContext) -> T, - { - self.cx.borrow_mut().add_view(window_id, build_view) - } - - pub fn add_option_view( - &mut self, - window_id: usize, - build_view: F, - ) -> Option> - where - T: View, - F: FnOnce(&mut ViewContext) -> Option, - { - self.cx.borrow_mut().add_option_view(window_id, build_view) - } - pub fn read T>(&self, callback: F) -> T { callback(self.cx.borrow().as_ref()) } @@ -786,14 +765,6 @@ impl AsyncAppContext { self.update(|cx| cx.add_model(build_model)) } - pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle - where - T: View, - F: FnOnce(&mut ViewContext) -> T, - { - self.update(|cx| cx.add_view(window_id, build_view)) - } - pub fn add_window( &mut self, window_options: WindowOptions, @@ -1021,6 +992,7 @@ impl MutableAppContext { cx: AppContext { models: Default::default(), views: Default::default(), + parents: Default::default(), windows: Default::default(), globals: Default::default(), element_states: Default::default(), @@ -1645,17 +1617,7 @@ impl MutableAppContext { ) -> impl Iterator, SmallVec<[&Binding; 1]>)> { let mut action_types: HashSet<_> = self.global_actions.keys().copied().collect(); - let presenter = self - .presenters_and_platform_windows - .get(&window_id) - .unwrap() - .0 - .clone(); - let mut dispatch_path = Vec::new(); - presenter - .borrow() - .compute_dispatch_path_from(view_id, &mut dispatch_path); - for view_id in dispatch_path { + for view_id in self.parents(window_id, view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { @@ -1684,9 +1646,8 @@ impl MutableAppContext { pub fn is_action_available(&self, action: &dyn Action) -> bool { let action_type = action.as_any().type_id(); if let Some(window_id) = self.cx.platform.key_window_id() { - if let Some((presenter, _)) = self.presenters_and_platform_windows.get(&window_id) { - let dispatch_path = presenter.borrow().dispatch_path(&self.cx); - for view_id in dispatch_path { + if let Some(focused_view_id) = self.focused_view_id(window_id) { + for view_id in self.parents(window_id, focused_view_id) { if let Some(view) = self.views.get(&(window_id, view_id)) { let view_type = view.as_any().type_id(); if let Some(actions) = self.actions.get(&view_type) { @@ -1724,83 +1685,76 @@ impl MutableAppContext { None } - pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: &dyn Action) { - let presenter = self - .presenters_and_platform_windows - .get(&window_id) - .unwrap() - .0 - .clone(); - let mut dispatch_path = Vec::new(); - presenter - .borrow() - .compute_dispatch_path_from(view_id, &mut dispatch_path); - self.dispatch_action_any(window_id, &dispatch_path, action); - } + // pub fn dispatch_action_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( + // pub fn dispatch_action( + // &mut self, + // window_id: usize, + // dispatch_path: Vec, + // action: &A, + // ) { + // self.dispatch_action_any(window_id, &dispatch_path, action); + // } + + // Traverses the parent tree. Walks down the tree toward the passed + // view calling visit with true. Then walks back up the tree calling visit with false. + // If `visit` returns false this function will immediately return. + // Returns a bool indicating if the traversal was completed early. + fn visit_dispatch_path( &mut self, window_id: usize, - dispatch_path: Vec, - action: &A, - ) { - self.dispatch_action_any(window_id, &dispatch_path, action); - } - - pub(crate) fn dispatch_action_any( - &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(); + // List of view ids from the leaf to the root of the window + let mut path = vec![view_id]; + let mut current_view = view_id; + while let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, current_view)) { + current_view = *parent_id; + path.push(current_view); + } - if 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); - - 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 + '_ { + std::iter::from_fn(move || { + if let Some(ParentId::View(parent_id)) = self.parents.get(&(window_id, view_id)) { + view_id = *parent_id; + Some(view_id) + } else { + None + } }) } - fn actions_mut( &mut self, capture_phase: bool, @@ -1836,34 +1790,34 @@ impl MutableAppContext { self.keystroke_matcher.clear_bindings(); } - pub fn dispatch_keystroke( - &mut self, - window_id: usize, - dispatch_path: Vec, - keystroke: &Keystroke, - ) -> bool { - let mut context_chain = Vec::new(); - for view_id in &dispatch_path { - let view = self - .cx - .views - .get(&(window_id, *view_id)) - .expect("view in responder chain does not exist"); - context_chain.push(view.keymap_context(self.as_ref())); - } - + pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: &Keystroke) -> bool { let mut pending = false; - for (i, cx) in context_chain.iter().enumerate().rev() { - match self - .keystroke_matcher - .push_keystroke(keystroke.clone(), dispatch_path[i], cx) - { - MatchResult::None => {} - MatchResult::Pending => pending = true, - MatchResult::Action(action) => { - if self.dispatch_action_any(window_id, &dispatch_path[0..=i], action.as_ref()) { - self.keystroke_matcher.clear_pending(); - return true; + + if let Some(view_id) = self.focused_view_id(window_id) { + for view_id in self.parents(window_id, view_id).collect::>() { + let keymap_context = self + .cx + .views + .get(&(window_id, view_id)) + .expect("View passed to visit does not exist") + .keymap_context(self.as_ref()); + + match self.keystroke_matcher.push_keystroke( + keystroke.clone(), + view_id, + &keymap_context, + ) { + MatchResult::None => {} + MatchResult::Pending => pending = true, + MatchResult::Action(action) => { + if self.handle_dispatch_action_any_effect( + window_id, + Some(view_id), + action.as_ref(), + ) { + self.keystroke_matcher.clear_pending(); + return true; + } } } } @@ -1917,15 +1871,14 @@ impl MutableAppContext { { self.update(|this| { let type_id = TypeId::of::(); - 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::()); + } }) } @@ -1955,7 +1908,9 @@ impl MutableAppContext { { self.update(|this| { let window_id = post_inc(&mut this.next_window_id); - let root_view = this.add_view(window_id, build_root_view); + let root_view = this + .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx))) + .unwrap(); this.cx.windows.insert( window_id, Window { @@ -1979,7 +1934,9 @@ impl MutableAppContext { F: FnOnce(&mut ViewContext) -> T, { self.update(|this| { - let root_view = this.add_view(window_id, build_root_view); + let root_view = this + .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx))) + .unwrap(); let window = this.cx.windows.get_mut(&window_id).unwrap(); window.root_view = root_view.clone().into(); window.focused_view_id = Some(root_view.id()); @@ -2009,11 +1966,7 @@ impl MutableAppContext { app.update(|cx| { if let Some(presenter) = presenter.upgrade() { if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event { - if cx.dispatch_keystroke( - window_id, - presenter.borrow().dispatch_path(cx.as_ref()), - keystroke, - ) { + if cx.dispatch_keystroke(window_id, keystroke) { return true; } } @@ -2079,18 +2032,45 @@ impl MutableAppContext { ) } - pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle + pub fn add_view( + &mut self, + parent_handle: impl Into, + build_view: F, + ) -> ViewHandle where T: View, F: FnOnce(&mut ViewContext) -> 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( + &mut self, + parent_handle: impl Into, + build_view: F, + ) -> Option> + where + T: View, + F: FnOnce(&mut ViewContext) -> Option, + { + 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( &mut self, window_id: usize, + parent_id: ParentId, build_view: F, ) -> Option> where @@ -2102,6 +2082,7 @@ impl MutableAppContext { let mut cx = ViewContext::new(this, window_id, view_id); let handle = if let Some(view) = build_view(&mut cx) { this.cx.views.insert((window_id, view_id), Box::new(view)); + this.cx.parents.insert((window_id, view_id), parent_id); if let Some(window) = this.cx.windows.get_mut(&window_id) { window .invalidation @@ -2154,6 +2135,7 @@ impl MutableAppContext { None } }); + self.cx.parents.remove(&(window_id, view_id)); if let Some(view_id) = change_focus_to { self.handle_focus_effect(window_id, Some(view_id)); @@ -2316,6 +2298,17 @@ impl MutableAppContext { Effect::RefreshWindows => { refreshing = true; } + Effect::DispatchActionFrom { + window_id, + view_id, + action, + } => { + self.handle_dispatch_action_any_effect( + window_id, + Some(view_id), + action.as_ref(), + ); + } Effect::ActionDispatchNotification { action_id } => { self.handle_action_dispatch_notification_effect(action_id) } @@ -2403,6 +2396,23 @@ impl MutableAppContext { self.pending_effects.push_back(Effect::RefreshWindows); } + pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) { + self.dispatch_any_action_at(window_id, view_id, Box::new(action)); + } + + pub fn dispatch_any_action_at( + &mut self, + window_id: usize, + view_id: usize, + action: Box, + ) { + self.pending_effects.push_back(Effect::DispatchActionFrom { + window_id, + view_id, + action, + }); + } + fn perform_window_refresh(&mut self) { let mut presenters = mem::take(&mut self.presenters_and_platform_windows); for (window_id, (presenter, window)) in &mut presenters { @@ -2569,6 +2579,55 @@ impl MutableAppContext { }) } + fn handle_dispatch_action_any_effect( + &mut self, + window_id: usize, + view_id: Option, + action: &dyn Action, + ) -> bool { + self.update(|this| { + if let Some(view_id) = view_id { + this.visit_dispatch_path(window_id, view_id, |view_id, capture_phase, this| { + if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { + let type_id = view.as_any().type_id(); + + if let Some((name, mut handlers)) = this + .actions_mut(capture_phase) + .get_mut(&type_id) + .and_then(|h| h.remove_entry(&action.id())) + { + for handler in handlers.iter_mut().rev() { + this.halt_action_dispatch = true; + handler(view.as_mut(), action, this, window_id, view_id); + if this.halt_action_dispatch { + break; + } + } + this.actions_mut(capture_phase) + .get_mut(&type_id) + .unwrap() + .insert(name, handlers); + } + + this.cx.views.insert((window_id, view_id), view); + } + + !this.halt_action_dispatch + }); + } + + if !this.halt_action_dispatch { + this.halt_action_dispatch = this.dispatch_global_action_any(action); + } + + this.pending_effects + .push_back(Effect::ActionDispatchNotification { + action_id: action.id(), + }); + this.halt_action_dispatch + }) + } + fn handle_action_dispatch_notification_effect(&mut self, action_id: TypeId) { let mut callbacks = mem::take(&mut *self.action_dispatch_observations.lock()); for (_, callback) in &mut callbacks { @@ -2750,9 +2809,15 @@ impl Deref for MutableAppContext { } } +pub enum ParentId { + View(usize), + Root, +} + pub struct AppContext { models: HashMap>, views: HashMap<(usize, usize), Box>, + parents: HashMap<(usize, usize), ParentId>, windows: HashMap, globals: HashMap>, element_states: HashMap>, @@ -2977,6 +3042,11 @@ pub enum Effect { callback: WindowFullscreenCallback, }, RefreshWindows, + DispatchActionFrom { + window_id: usize, + view_id: usize, + action: Box, + }, ActionDispatchNotification { action_id: TypeId, }, @@ -3060,6 +3130,13 @@ impl Debug for Effect { .field("view_id", view_id) .field("subscription_id", subscription_id) .finish(), + Effect::DispatchActionFrom { + window_id, view_id, .. + } => f + .debug_struct("Effect::DispatchActionFrom") + .field("window_id", window_id) + .field("view_id", view_id) + .finish(), Effect::ActionDispatchNotification { action_id, .. } => f .debug_struct("Effect::ActionDispatchNotification") .field("action_id", action_id) @@ -3640,7 +3717,11 @@ impl<'a, T: View> ViewContext<'a, T> { S: View, F: FnOnce(&mut ViewContext) -> S, { - 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(&mut self, build_view: F) -> Option> @@ -3648,7 +3729,8 @@ impl<'a, T: View> ViewContext<'a, T> { S: View, F: FnOnce(&mut ViewContext) -> Option, { - self.app.add_option_view(self.window_id, build_view) + self.app + .build_and_insert_view(self.window_id, ParentId::View(self.view_id), build_view) } pub fn replace_root_view(&mut self, build_root_view: F) -> ViewHandle @@ -3658,7 +3740,9 @@ impl<'a, T: View> ViewContext<'a, T> { { let window_id = self.window_id; self.update(|this| { - let root_view = this.add_view(window_id, build_root_view); + let root_view = this + .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx))) + .unwrap(); let window = this.cx.windows.get_mut(&window_id).unwrap(); window.root_view = root_view.clone().into(); window.focused_view_id = Some(root_view.id()); @@ -3802,6 +3886,11 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.notify_view(self.window_id, self.view_id); } + pub fn dispatch_any_action(&mut self, action: Box) { + 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)) { let handle = self.handle(); self.app.defer(move |cx| { @@ -5797,9 +5886,9 @@ mod tests { } } - let (window_id, _) = cx.add_window(Default::default(), |cx| View::new(None, cx)); - let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx)); - let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx)); + let (_, root_view) = cx.add_window(Default::default(), |cx| View::new(None, cx)); + let handle_1 = cx.add_view(&root_view, |cx| View::new(None, cx)); + let handle_2 = cx.add_view(&root_view, |cx| View::new(Some(handle_1.clone()), cx)); assert_eq!(cx.cx.views.len(), 3); handle_1.update(cx, |view, cx| { @@ -5973,8 +6062,8 @@ mod tests { type Event = usize; } - let (window_id, handle_1) = cx.add_window(Default::default(), |_| View::default()); - let handle_2 = cx.add_view(window_id, |_| View::default()); + let (_, handle_1) = cx.add_window(Default::default(), |_| View::default()); + let handle_2 = cx.add_view(&handle_1, |_| View::default()); let handle_3 = cx.add_model(|_| Model); handle_1.update(cx, |_, cx| { @@ -6214,9 +6303,9 @@ mod tests { type Event = (); } - let (window_id, _) = cx.add_window(Default::default(), |_| View); - let observing_view = cx.add_view(window_id, |_| View); - let emitting_view = cx.add_view(window_id, |_| View); + let (_, root_view) = cx.add_window(Default::default(), |_| View); + let observing_view = cx.add_view(&root_view, |_| View); + let emitting_view = cx.add_view(&root_view, |_| View); let observing_model = cx.add_model(|_| Model); let observed_model = cx.add_model(|_| Model); @@ -6390,8 +6479,8 @@ mod tests { type Event = (); } - let (window_id, _) = cx.add_window(Default::default(), |_| View); - let observing_view = cx.add_view(window_id, |_| View); + let (_, root_view) = cx.add_window(Default::default(), |_| View); + let observing_view = cx.add_view(root_view, |_| View); let observing_model = cx.add_model(|_| Model); let observed_model = cx.add_model(|_| Model); @@ -6513,9 +6602,9 @@ mod tests { } } - let (window_id, _) = cx.add_window(Default::default(), |_| View); - let observing_view = cx.add_view(window_id, |_| View); - let observed_view = cx.add_view(window_id, |_| View); + let (_, root_view) = cx.add_window(Default::default(), |_| View); + let observing_view = cx.add_view(&root_view, |_| View); + let observed_view = cx.add_view(&root_view, |_| View); let observation_count = Rc::new(RefCell::new(0)); observing_view.update(cx, |_, cx| { @@ -6587,11 +6676,11 @@ mod tests { } let view_events: Arc>> = Default::default(); - let (window_id, view_1) = cx.add_window(Default::default(), |_| View { + let (_, view_1) = cx.add_window(Default::default(), |_| View { events: view_events.clone(), name: "view 1".to_string(), }); - let view_2 = cx.add_view(window_id, |_| View { + let view_2 = cx.add_view(&view_1, |_| View { events: view_events.clone(), name: "view 2".to_string(), }); @@ -6813,11 +6902,6 @@ mod tests { } }); - let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 }); - let view_2 = cx.add_view(window_id, |_| ViewB { id: 2 }); - let view_3 = cx.add_view(window_id, |_| ViewA { id: 3 }); - let view_4 = cx.add_view(window_id, |_| ViewB { id: 4 }); - let observed_actions = Rc::new(RefCell::new(Vec::new())); cx.observe_actions({ let observed_actions = observed_actions.clone(); @@ -6825,9 +6909,14 @@ mod tests { }) .detach(); - cx.dispatch_action( + let (window_id, view_1) = cx.add_window(Default::default(), |_| ViewA { id: 1 }); + let view_2 = cx.add_view(&view_1, |_| ViewB { id: 2 }); + let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 }); + let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 }); + + cx.handle_dispatch_action_any_effect( window_id, - vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()], + Some(view_4.id()), &Action("bar".to_string()), ); @@ -6848,10 +6937,15 @@ mod tests { assert_eq!(*observed_actions.borrow(), [Action::default().id()]); // Remove view_1, which doesn't propagate the action + + let (window_id, view_2) = cx.add_window(Default::default(), |_| ViewA { id: 1 }); + let view_3 = cx.add_view(&view_2, |_| ViewA { id: 3 }); + let view_4 = cx.add_view(&view_3, |_| ViewB { id: 4 }); + actions.borrow_mut().clear(); - cx.dispatch_action( + cx.handle_dispatch_action_any_effect( window_id, - vec![view_2.id(), view_3.id(), view_4.id()], + Some(view_4.id()), &Action("bar".to_string()), ); @@ -6924,8 +7018,9 @@ mod tests { view_3.keymap_context.set.insert("c".into()); let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1); - let view_2 = cx.add_view(window_id, |_| view_2); - let view_3 = cx.add_view(window_id, |_| view_3); + let view_2 = cx.add_view(&view_1, |_| view_2); + let view_3 = cx.add_view(&view_2, |_| view_3); + cx.focus(window_id, Some(view_3.id())); // This keymap's only binding dispatches an action on view 2 because that view will have // "a" and "b" in its context, but not "c". @@ -6963,20 +7058,12 @@ mod tests { } }); - cx.dispatch_keystroke( - window_id, - vec![view_1.id(), view_2.id(), view_3.id()], - &Keystroke::parse("a").unwrap(), - ); + cx.dispatch_keystroke(window_id, &Keystroke::parse("a").unwrap()); assert_eq!(&*actions.borrow(), &["2 a"]); actions.borrow_mut().clear(); - cx.dispatch_keystroke( - window_id, - vec![view_1.id(), view_2.id(), view_3.id()], - &Keystroke::parse("b").unwrap(), - ); + cx.dispatch_keystroke(window_id, &Keystroke::parse("b").unwrap()); assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]); } @@ -7130,8 +7217,8 @@ mod tests { } } - let window_id = cx.add_window(|_| View).0; - let view = cx.add_view(window_id, |_| View); + let (_, root_view) = cx.add_window(|_| View); + let view = cx.add_view(&root_view, |_| View); let condition = view.condition(&cx, |_, _| false); cx.update(|_| drop(view)); @@ -7164,7 +7251,7 @@ mod tests { Some("render count: 0") ); - let view = cx.add_view(window_id, |cx| { + let view = cx.add_view(&root_view, |cx| { cx.refresh_windows(); View(0) }); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index a8aefad6e9..57d3a1a3fd 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -26,7 +26,6 @@ use std::{ pub struct Presenter { window_id: usize, pub(crate) rendered_views: HashMap, - parents: HashMap, cursor_regions: Vec, mouse_regions: Vec<(MouseRegion, usize)>, font_cache: Arc, @@ -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 { - 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 { + // 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) { - 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) { + // 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, - pub action: Box, -} - pub struct LayoutContext<'a> { window_id: usize, rendered_views: &'a mut HashMap, - parents: &'a mut HashMap, view_stack: Vec, pub font_cache: &'a Arc, pub font_system: Arc, @@ -506,9 +482,6 @@ impl<'a> LayoutContext<'a> { } fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { - if let Some(parent_id) = self.view_stack.last() { - self.parents.insert(view_id, *parent_id); - } self.view_stack.push(view_id); let mut rendered_view = self.rendered_views.remove(&view_id).unwrap(); let size = rendered_view.layout(constraint, self); @@ -637,7 +610,6 @@ impl<'a> Deref for PaintContext<'a> { pub struct EventContext<'a> { rendered_views: &'a mut HashMap, - dispatched_actions: Vec, pub font_cache: &'a FontCache, pub text_layout_cache: &'a TextLayoutCache, pub app: &'a mut MutableAppContext, @@ -692,10 +664,8 @@ impl<'a> EventContext<'a> { } pub fn dispatch_any_action(&mut self, action: Box) { - 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(&mut self, action: A) { diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index c8a69c4e7d..4122ad09b7 100644 --- a/crates/gpui/src/test.rs +++ b/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) -> ElementBox { + Element::boxed(Empty::new()) + } +} diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 52631e71b4..ada785f854 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -600,7 +600,7 @@ impl BufferSearchBar { mod tests { use super::*; use editor::{DisplayPoint, Editor}; - use gpui::{color::Color, TestAppContext}; + use gpui::{color::Color, test::EmptyView, TestAppContext}; use language::Buffer; use std::sync::Arc; use unindent::Unindent as _; @@ -629,11 +629,13 @@ mod tests { cx, ) }); - let editor = cx.add_view(Default::default(), |cx| { + let (_, root_view) = cx.add_window(|_| EmptyView); + + let editor = cx.add_view(&root_view, |cx| { Editor::for_buffer(buffer.clone(), None, cx) }); - let search_bar = cx.add_view(Default::default(), |cx| { + let search_bar = cx.add_view(&root_view, |cx| { let mut search_bar = BufferSearchBar::new(cx); search_bar.set_active_pane_item(Some(&editor), cx); search_bar.show(false, true, cx); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 39415b3832..6cbd4c7e2d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -933,7 +933,8 @@ mod tests { cx.update(|cx| { let mut settings = Settings::test(cx); settings.theme = Arc::new(theme); - cx.set_global(settings) + cx.set_global(settings); + cx.set_global(ActiveSearches::default()); }); let fs = FakeFs::new(cx.background()); @@ -949,9 +950,7 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let search = cx.add_model(|cx| ProjectSearch::new(project, cx)); - let search_view = cx.add_view(Default::default(), |cx| { - ProjectSearchView::new(search.clone(), cx) - }); + let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx)); search_view.update(cx, |search_view, cx| { search_view diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a05b9ac1a8..354331974f 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -386,7 +386,7 @@ impl Pane { project_entry_id: ProjectEntryId, focus_item: bool, cx: &mut ViewContext, - build_item: impl FnOnce(&mut MutableAppContext) -> Box, + build_item: impl FnOnce(&mut ViewContext) -> Box, ) -> Box { let existing_item = pane.update(cx, |pane, cx| { for (ix, item) in pane.items.iter().enumerate() { @@ -403,7 +403,7 @@ impl Pane { if let Some(existing_item) = existing_item { existing_item } else { - let item = build_item(cx); + let item = pane.update(cx, |_, cx| build_item(cx)); Self::add_item(workspace, pane, item.boxed_clone(), true, focus_item, cx); item } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a27754aa13..d1a239cbb4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -59,7 +59,7 @@ use waiting_room::WaitingRoom; type ProjectItemBuilders = HashMap< TypeId, - fn(usize, ModelHandle, AnyModelHandle, &mut MutableAppContext) -> Box, + fn(ModelHandle, AnyModelHandle, &mut ViewContext) -> Box, >; type FollowableItemBuilder = fn( @@ -219,9 +219,9 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { pub fn register_project_item(cx: &mut MutableAppContext) { cx.update_default_global(|builders: &mut ProjectItemBuilders, _| { - builders.insert(TypeId::of::(), |window_id, project, model, cx| { + builders.insert(TypeId::of::(), |project, model, cx| { let item = model.downcast::().unwrap(); - Box::new(cx.add_view(window_id, |cx| I::for_project_item(project, item, cx))) + Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx))) }); }); } @@ -1475,12 +1475,11 @@ impl Workspace { ) -> Task< Result<( ProjectEntryId, - impl 'static + FnOnce(&mut MutableAppContext) -> Box, + impl 'static + FnOnce(&mut ViewContext) -> Box, )>, > { let project = self.project().clone(); let project_item = project.update(cx, |project, cx| project.open_path(path, cx)); - let window_id = cx.window_id(); cx.as_mut().spawn(|mut cx| async move { let (project_entry_id, project_item) = project_item.await?; let build_item = cx.update(|cx| { @@ -1490,7 +1489,7 @@ impl Workspace { .cloned() })?; let build_item = - move |cx: &mut MutableAppContext| build_item(window_id, project, project_item, cx); + move |cx: &mut ViewContext| build_item(project, project_item, cx); Ok((project_entry_id, build_item)) }) } @@ -2732,7 +2731,7 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { (app_state.initialize_workspace)(&mut workspace, app_state, cx); workspace }); - cx.dispatch_action(window_id, vec![workspace.id()], &NewFile); + cx.dispatch_action_at(window_id, workspace.id(), NewFile); } #[cfg(test)] @@ -2751,10 +2750,10 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); // Adding an item with no ambiguity renders the tab without detail. - let item1 = cx.add_view(window_id, |_| { + let item1 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]); item @@ -2766,7 +2765,7 @@ mod tests { // Adding an item that creates ambiguity increases the level of detail on // both tabs. - let item2 = cx.add_view(window_id, |_| { + let item2 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); item @@ -2780,7 +2779,7 @@ mod tests { // Adding an item that creates ambiguity increases the level of detail only // on the ambiguous tabs. In this case, the ambiguity can't be resolved so // we stop at the highest detail available. - let item3 = cx.add_view(window_id, |_| { + let item3 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]); item @@ -2820,12 +2819,12 @@ mod tests { project.worktrees(cx).next().unwrap().read(cx).id() }); - let item1 = cx.add_view(window_id, |_| { + let item1 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.project_path = Some((worktree_id, "one.txt").into()); item }); - let item2 = cx.add_view(window_id, |_| { + let item2 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.project_path = Some((worktree_id, "two.txt").into()); item @@ -2914,19 +2913,19 @@ mod tests { let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); // When there are no dirty items, there's nothing to do. - let item1 = cx.add_view(window_id, |_| TestItem::new()); + let item1 = cx.add_view(&workspace, |_| TestItem::new()); workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx)); let task = workspace.update(cx, |w, cx| w.prepare_to_close(cx)); assert_eq!(task.await.unwrap(), true); // When there are dirty untitled items, prompt to save each one. If the user // cancels any prompt, then abort. - let item2 = cx.add_view(window_id, |_| { + let item2 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item }); - let item3 = cx.add_view(window_id, |_| { + let item3 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; @@ -2953,27 +2952,27 @@ mod tests { let project = Project::test(fs, None, cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); - let item1 = cx.add_view(window_id, |_| { + let item1 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; item }); - let item2 = cx.add_view(window_id, |_| { + let item2 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item.has_conflict = true; item.project_entry_ids = vec![ProjectEntryId::from_proto(2)]; item }); - let item3 = cx.add_view(window_id, |_| { + let item3 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item.has_conflict = true; item.project_entry_ids = vec![ProjectEntryId::from_proto(3)]; item }); - let item4 = cx.add_view(window_id, |_| { + let item4 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.is_dirty = true; item @@ -3144,7 +3143,7 @@ mod tests { let project = Project::test(fs, [], cx).await; let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); - let item = cx.add_view(window_id, |_| { + let item = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; item @@ -3259,9 +3258,9 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); - let item = cx.add_view(window_id, |_| { + let item = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); item.project_entry_ids = vec![ProjectEntryId::from_proto(1)]; item