diff --git a/crates/collab/tests/integration/editor_tests.rs b/crates/collab/tests/integration/editor_tests.rs index a48e43741641b92cedaf4e6d4d6bd80ad6f68c19..0d0569182d5a9ff235642d61c39f0b5bc15b6cb0 100644 --- a/crates/collab/tests/integration/editor_tests.rs +++ b/crates/collab/tests/integration/editor_tests.rs @@ -107,7 +107,7 @@ async fn test_host_disconnect( cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b); let workspace_b = window_b diff --git a/crates/collab/tests/integration/git_tests.rs b/crates/collab/tests/integration/git_tests.rs index 63cee5886d5096cb0e3fbee3886b90f66c675bfa..f3abb5bc3f3e1a12e7ecb56c985f2cff46582cee 100644 --- a/crates/collab/tests/integration/git_tests.rs +++ b/crates/collab/tests/integration/git_tests.rs @@ -67,7 +67,7 @@ async fn test_project_diff(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let cx_b = &mut VisualTestContext::from_window(*window_b, cx_b); let workspace_b = window_b diff --git a/crates/collab/tests/integration/test_server.rs b/crates/collab/tests/integration/test_server.rs index d822a087d96fdc119cc700f2f0e8f79d16b95acf..6bc02433e2f724d96b7911a3ed3c741377b5e70f 100644 --- a/crates/collab/tests/integration/test_server.rs +++ b/crates/collab/tests/integration/test_server.rs @@ -886,7 +886,7 @@ impl TestClient { let window = cx.add_window(|window, cx| { window.activate_window(); let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx)); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let cx = VisualTestContext::from_window(*window, cx).into_mut(); cx.run_until_parked(); @@ -905,7 +905,7 @@ impl TestClient { let window = cx.add_window(|window, cx| { window.activate_window(); let workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx)); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let cx = VisualTestContext::from_window(*window, cx).into_mut(); let workspace = window diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs index a14336e0058ea64b8a78deae78d98df9c34d3dd9..a871df88bc30c42248eb5e3f5cfdb27e880703e9 100644 --- a/crates/git_ui/src/worktree_picker.rs +++ b/crates/git_ui/src/worktree_picker.rs @@ -518,7 +518,7 @@ async fn open_remote_worktree( workspace.centered_layout = workspace_position.centered_layout; workspace }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) })? }; diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index fa193a22d99a71981d085dec8e7334744d7e7d65..d693a9cdf35439e3bab10fd9a3c5892c149e56ea 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -233,7 +233,7 @@ pub async fn open_remote_project( workspace.centered_layout = workspace_position.centered_layout; workspace }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) })?; let workspace = window.update(cx, |multi_workspace, _, _cx| { multi_workspace.workspace().clone() diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 6e2d9ce226c8f15552963bf457e622141f87cec7..8bddcf37270e56932e75635fcd35616d12309b6e 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -495,7 +495,7 @@ impl ProjectPicker { telemetry::event!("SSH Project Created"); Workspace::new(None, project.clone(), app_state.clone(), window, cx) }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) }) .log_err()?; diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 2ec92f3fbf8ea39948547cd6595316c77c6497af..ef0e05eabada3f91c3d66ddfd3c803dfb55ada92 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -4800,7 +4800,7 @@ pub mod test { cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let (_multi_workspace2, cx) = cx.add_window_view(|window, cx| { @@ -4813,7 +4813,7 @@ pub mod test { cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let workspace2_handle = cx.window_handle().downcast::().unwrap(); @@ -4945,7 +4945,7 @@ pub mod test { cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); let workspace1_handle = cx.window_handle().downcast::().unwrap(); @@ -4995,7 +4995,7 @@ pub mod test { cx, ) }); - MultiWorkspace::new(workspace, cx) + MultiWorkspace::new(workspace, window, cx) }); cx.run_until_parked(); diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 6f853bfae20f4e79ce1a17338d9d9ad6e79af42c..2a528d21e778876e1fb83145065ae2cdbce45a87 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -2,8 +2,8 @@ use anyhow::Result; use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt}; use gpui::{ AnyView, App, Context, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable, - ManagedView, MouseButton, Pixels, Render, Subscription, Task, Tiling, Window, actions, - deferred, px, + ManagedView, MouseButton, Pixels, Render, Subscription, Task, Tiling, Window, WindowId, + actions, deferred, px, }; use project::Project; use std::path::PathBuf; @@ -93,6 +93,7 @@ impl SidebarHandle for Entity { } pub struct MultiWorkspace { + window_id: WindowId, workspaces: Vec>, active_workspace_index: usize, sidebar: Option>, @@ -101,8 +102,9 @@ pub struct MultiWorkspace { } impl MultiWorkspace { - pub fn new(workspace: Entity, _cx: &mut Context) -> Self { + pub fn new(workspace: Entity, window: &mut Window, _cx: &mut Context) -> Self { Self { + window_id: window.window_handle().window_id(), workspaces: vec![workspace], active_workspace_index: 0, sidebar: None, @@ -154,7 +156,7 @@ impl MultiWorkspace { if self.sidebar_open { self.close_sidebar(window, cx); } else { - self.open_sidebar(window, cx); + self.open_sidebar(cx); if let Some(sidebar) = &self.sidebar { sidebar.focus(window, cx); } @@ -180,21 +182,21 @@ impl MultiWorkspace { sidebar.focus(window, cx); } } else { - self.open_sidebar(window, cx); + self.open_sidebar(cx); if let Some(sidebar) = &self.sidebar { sidebar.focus(window, cx); } } } - pub fn open_sidebar(&mut self, window: &mut Window, cx: &mut Context) { + pub fn open_sidebar(&mut self, cx: &mut Context) { self.sidebar_open = true; for workspace in &self.workspaces { workspace.update(cx, |workspace, cx| { workspace.set_workspace_sidebar_open(true, cx); }); } - self.serialize(window, cx); + self.serialize(cx); cx.notify(); } @@ -208,7 +210,7 @@ impl MultiWorkspace { let pane = self.workspace().read(cx).active_pane().clone(); let pane_focus = pane.read(cx).focus_handle(cx); window.focus(&pane_focus, cx); - self.serialize(window, cx); + self.serialize(cx); cx.notify(); } @@ -239,6 +241,7 @@ impl MultiWorkspace { let index = self.add_workspace(workspace, cx); if self.active_workspace_index != index { self.active_workspace_index = index; + self.serialize(cx); cx.notify(); } } @@ -266,7 +269,7 @@ impl MultiWorkspace { "workspace index out of bounds" ); self.active_workspace_index = index; - self.serialize(window, cx); + self.serialize(cx); self.focus_active_workspace(window, cx); cx.notify(); } @@ -289,8 +292,8 @@ impl MultiWorkspace { } } - fn serialize(&self, window: &mut Window, cx: &mut App) { - let window_id = window.window_handle().window_id(); + fn serialize(&self, cx: &mut App) { + let window_id = self.window_id; let state = crate::persistence::model::MultiWorkspaceState { active_workspace_id: self.workspace().read(cx).database_id(), sidebar_open: self.sidebar_open, @@ -404,7 +407,7 @@ impl MultiWorkspace { #[cfg(any(test, feature = "test-support"))] pub fn test_new(project: Entity, window: &mut Window, cx: &mut Context) -> Self { let workspace = cx.new(|cx| Workspace::test_new(project, window, cx)); - Self::new(workspace, cx) + Self::new(workspace, window, cx) } #[cfg(any(test, feature = "test-support"))] @@ -453,6 +456,7 @@ impl MultiWorkspace { } self.focus_active_workspace(window, cx); + self.serialize(cx); cx.notify(); } diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 785f3fd9f32aa3bb8d12d6d4dd87cfb6bfe3a1e7..bcff7bc24d5fe49e41b3ce3eda057323819f5589 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -2355,6 +2355,72 @@ mod tests { use serde_json::json; use std::{thread, time::Duration}; + #[gpui::test] + async fn test_multi_workspace_serializes_on_add_and_remove(cx: &mut gpui::TestAppContext) { + use crate::multi_workspace::MultiWorkspace; + use crate::persistence::read_multi_workspace_state; + use feature_flags::FeatureFlagAppExt; + use gpui::AppContext as _; + use project::Project; + + crate::tests::init_test(cx); + + cx.update(|cx| { + cx.set_staff(true); + cx.update_flags(true, vec!["agent-v2".to_string()]); + }); + + let fs = fs::FakeFs::new(cx.executor()); + let project1 = Project::test(fs.clone(), [], cx).await; + let project2 = Project::test(fs.clone(), [], cx).await; + + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project1.clone(), window, cx)); + + multi_workspace.update_in(cx, |mw, _, cx| { + mw.set_random_database_id(cx); + }); + + let window_id = + multi_workspace.update_in(cx, |_, window, _cx| window.window_handle().window_id()); + + // --- Add a second workspace --- + let workspace2 = multi_workspace.update_in(cx, |mw, window, cx| { + let workspace = cx.new(|cx| crate::Workspace::test_new(project2.clone(), window, cx)); + workspace.update(cx, |ws, _cx| ws.set_random_database_id()); + mw.activate(workspace.clone(), cx); + workspace + }); + + // Run background tasks so serialize has a chance to flush. + cx.run_until_parked(); + + // Read back the persisted state and check that the active workspace ID was written. + let state_after_add = read_multi_workspace_state(window_id); + let active_workspace2_db_id = workspace2.read_with(cx, |ws, _| ws.database_id()); + assert_eq!( + state_after_add.active_workspace_id, active_workspace2_db_id, + "After adding a second workspace, the serialized active_workspace_id should match \ + the newly activated workspace's database id" + ); + + // --- Remove the second workspace (index 1) --- + multi_workspace.update_in(cx, |mw, window, cx| { + mw.remove_workspace(1, window, cx); + }); + + cx.run_until_parked(); + + let state_after_remove = read_multi_workspace_state(window_id); + let remaining_db_id = + multi_workspace.read_with(cx, |mw, cx| mw.workspace().read(cx).database_id()); + assert_eq!( + state_after_remove.active_workspace_id, remaining_db_id, + "After removing a workspace, the serialized active_workspace_id should match \ + the remaining active workspace's database id" + ); + } + #[gpui::test] async fn test_breakpoints() { zlog::init_test(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 09242c49b24ea048b7fca2f1e48e28651af4b345..aad36367f933cb54697bf637b9a58e32cc9d5039 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1876,7 +1876,7 @@ impl Workspace { workspace }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) } })?; let workspace = @@ -7972,8 +7972,8 @@ pub async fn restore_multiworkspace( if state.sidebar_open { window_handle - .update(cx, |multi_workspace, window, cx| { - multi_workspace.open_sidebar(window, cx); + .update(cx, |multi_workspace, _, cx| { + multi_workspace.open_sidebar(cx); }) .ok(); } @@ -8536,7 +8536,7 @@ pub fn open_workspace_by_id( workspace.centered_layout = centered_layout; workspace }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) } })?; @@ -9053,7 +9053,7 @@ pub fn join_in_room_project( let workspace = cx.new(|cx| { Workspace::new(Default::default(), project, app_state.clone(), window, cx) }); - cx.new(|cx| MultiWorkspace::new(workspace, cx)) + cx.new(|cx| MultiWorkspace::new(workspace, window, cx)) }) })? }; diff --git a/crates/zed/src/visual_test_runner.rs b/crates/zed/src/visual_test_runner.rs index 7db6602af8f0f474728c0806c6931f0704e99754..fea329d27c3946bd62e4a00c370b2a012c991c25 100644 --- a/crates/zed/src/visual_test_runner.rs +++ b/crates/zed/src/visual_test_runner.rs @@ -2574,7 +2574,7 @@ fn run_multi_workspace_sidebar_visual_tests( Workspace::new(None, project2.clone(), app_state.clone(), window, cx) }); cx.new(|cx| { - let mut multi_workspace = MultiWorkspace::new(workspace1, cx); + let mut multi_workspace = MultiWorkspace::new(workspace1, window, cx); multi_workspace.activate(workspace2, cx); multi_workspace })