diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bf783999148c68466b3e4da8f802da22fb82617a..f5ced700b63c370814caa4b538c99366d654395f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1431,8 +1431,8 @@ impl MutableAppContext { 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 + /// Returns an iterator over all of the view ids from the passed view up to the root of the window + /// Includes the passed view itself fn ancestors(&self, window_id: usize, mut view_id: usize) -> impl Iterator + '_ { std::iter::once(view_id) .into_iter() @@ -3695,6 +3695,7 @@ impl<'a, T: View> ViewContext<'a, T> { return false; } self.ancestors(view.window_id, view.view_id) + .skip(1) // Skip self id .any(|parent| parent == self.view_id) } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 0879166bbe733faf5b9ee0e86695cf3bfe391e39..a9cfc3fc72829fd73183d815c6e93dceebd3890c 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -453,14 +453,26 @@ impl StatusItemView for ToggleDockButton { #[cfg(test)] mod tests { - use std::ops::{Deref, DerefMut}; + use std::{ + ops::{Deref, DerefMut}, + path::PathBuf, + }; use gpui::{AppContext, TestAppContext, UpdateView, ViewContext}; use project::{FakeFs, Project}; use settings::Settings; use super::*; - use crate::{item::test::TestItem, sidebar::Sidebar, ItemHandle, Workspace}; + use crate::{ + dock, + item::test::TestItem, + persistence::model::{ + SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace, + }, + register_deserializable_item, + sidebar::Sidebar, + ItemHandle, Workspace, + }; pub fn default_item_factory( _workspace: &mut Workspace, @@ -469,6 +481,51 @@ mod tests { Box::new(cx.add_view(|_| TestItem::new())) } + #[gpui::test] + async fn test_dock_workspace_infinite_loop(cx: &mut TestAppContext) { + cx.foreground().forbid_parking(); + Settings::test_async(cx); + + cx.update(|cx| { + register_deserializable_item::(cx); + }); + + let serialized_workspace = SerializedWorkspace { + id: 0, + location: Vec::::new().into(), + dock_position: dock::DockPosition::Shown(DockAnchor::Expanded), + center_group: SerializedPaneGroup::Pane(SerializedPane { + active: false, + children: vec![], + }), + dock_pane: SerializedPane { + active: true, + children: vec![SerializedItem { + active: true, + item_id: 0, + kind: "test".into(), + }], + }, + left_sidebar_open: false, + }; + + let fs = FakeFs::new(cx.background()); + let project = Project::test(fs, [], cx).await; + + let (_, _workspace) = cx.add_window(|cx| { + Workspace::new( + Some(serialized_workspace), + 0, + project.clone(), + default_item_factory, + cx, + ) + }); + + cx.foreground().run_until_parked(); + //Should terminate + } + #[gpui::test] async fn test_dock_hides_when_pane_empty(cx: &mut TestAppContext) { let mut cx = DockTestContext::new(cx).await; diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index e44e7ca09d0de00470ed8a211928c5f556391c2d..14f847fd54b429f43e5aaa0ea352588995f2532c 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -681,6 +681,7 @@ pub(crate) mod test { use super::{Item, ItemEvent}; pub struct TestItem { + pub workspace_id: WorkspaceId, pub state: String, pub label: String, pub save_count: usize, @@ -716,6 +717,7 @@ pub(crate) mod test { nav_history: None, tab_descriptions: None, tab_detail: Default::default(), + workspace_id: self.workspace_id, } } } @@ -736,9 +738,16 @@ pub(crate) mod test { nav_history: None, tab_descriptions: None, tab_detail: Default::default(), + workspace_id: 0, } } + pub fn new_deserialized(id: WorkspaceId) -> Self { + let mut this = Self::new(); + this.workspace_id = id; + this + } + pub fn with_label(mut self, state: &str) -> Self { self.label = state.to_string(); self @@ -893,11 +902,12 @@ pub(crate) mod test { fn deserialize( _project: ModelHandle, _workspace: WeakViewHandle, - _workspace_id: WorkspaceId, + workspace_id: WorkspaceId, _item_id: ItemId, - _cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Task>> { - unreachable!("Cannot deserialize test item") + let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id)); + Task::Ready(Some(anyhow::Ok(view))) } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a0c353b3f808bf1f1a5c9a9909f2047139916449..f613ed94c7649669e8493aa36366908b18c58806 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2371,7 +2371,12 @@ impl Workspace { workspace.toggle_sidebar(SidebarSide::Left, cx); } - // Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx); + // Note that without after_window, the focus_self() and + // the focus the dock generates start generating alternating + // focus due to the deferred execution each triggering each other + cx.after_window_update(move |workspace, cx| { + Dock::set_dock_position(workspace, serialized_workspace.dock_position, cx); + }); cx.notify(); }); @@ -2537,7 +2542,7 @@ impl View for Workspace { } else { for pane in self.panes() { let view = view.clone(); - if pane.update(cx, |_, cx| cx.is_child(view)) { + if pane.update(cx, |_, cx| view.id() == cx.view_id() || cx.is_child(view)) { self.handle_pane_focused(pane.clone(), cx); break; }