fix following bugs (#7406)

Conrad Irwin created

- Another broken following test
- Fix following between two windows

Release Notes:

- Fixed following when the leader has multiple Zed windows open

Change summary

crates/collab/src/tests/following_tests.rs   | 81 ++++++++++++++++++++++
crates/collab/src/tests/test_server.rs       |  7 +
crates/collab_ui/src/collab_titlebar_item.rs | 25 ++++--
crates/workspace/src/item.rs                 |  1 
crates/workspace/src/workspace.rs            | 26 +++---
5 files changed, 119 insertions(+), 21 deletions(-)

Detailed changes

crates/collab/src/tests/following_tests.rs 🔗

@@ -22,6 +22,8 @@ use workspace::{
     SplitDirection, Workspace,
 };
 
+use super::TestClient;
+
 #[gpui::test(iterations = 10)]
 async fn test_basic_following(
     cx_a: &mut TestAppContext,
@@ -1996,3 +1998,82 @@ async fn test_following_to_channel_notes_without_a_shared_project(
         );
     });
 }
+
+async fn join_channel(
+    channel_id: u64,
+    client: &TestClient,
+    cx: &mut TestAppContext,
+) -> anyhow::Result<()> {
+    cx.update(|cx| workspace::join_channel(channel_id, client.app_state.clone(), None, cx))
+        .await
+}
+
+async fn share_workspace(
+    workspace: &View<Workspace>,
+    cx: &mut VisualTestContext,
+) -> anyhow::Result<u64> {
+    let project = workspace.update(cx, |workspace, _| workspace.project().clone());
+    cx.read(ActiveCall::global)
+        .update(cx, |call, cx| call.share_project(project, cx))
+        .await
+}
+
+#[gpui::test]
+async fn test_following_to_channel_notes_other_workspace(
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let (_, client_a, client_b, channel) = TestServer::start2(cx_a, cx_b).await;
+
+    let mut cx_a2 = cx_a.clone();
+    let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await;
+    join_channel(channel, &client_a, cx_a).await.unwrap();
+    share_workspace(&workspace_a, cx_a).await.unwrap();
+
+    // a opens 1.txt
+    cx_a.simulate_keystrokes("cmd-p 1 enter");
+    cx_a.run_until_parked();
+    workspace_a.update(cx_a, |workspace, cx| {
+        let editor = workspace.active_item(cx).unwrap();
+        assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
+    });
+
+    // b joins channel and is following a
+    join_channel(channel, &client_b, cx_b).await.unwrap();
+    cx_b.run_until_parked();
+    let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
+    workspace_b.update(cx_b, |workspace, cx| {
+        let editor = workspace.active_item(cx).unwrap();
+        assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
+    });
+
+    // a opens a second workspace and the channel notes
+    let (workspace_a2, cx_a2) = client_a.build_test_workspace(&mut cx_a2).await;
+    cx_a2.update(|cx| cx.activate_window());
+    cx_a2
+        .update(|cx| ChannelView::open(channel, None, workspace_a2, cx))
+        .await
+        .unwrap();
+    cx_a2.run_until_parked();
+
+    // b should follow a to the channel notes
+    workspace_b.update(cx_b, |workspace, cx| {
+        let editor = workspace.active_item_as::<ChannelView>(cx).unwrap();
+        assert_eq!(editor.read(cx).channel(cx).unwrap().id, channel);
+    });
+
+    // a returns to the shared project
+    cx_a.update(|cx| cx.activate_window());
+    cx_a.run_until_parked();
+
+    workspace_a.update(cx_a, |workspace, cx| {
+        let editor = workspace.active_item(cx).unwrap();
+        assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
+    });
+
+    // b should follow a back
+    workspace_b.update(cx_b, |workspace, cx| {
+        let editor = workspace.active_item_as::<Editor>(cx).unwrap();
+        assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
+    });
+}

crates/collab/src/tests/test_server.rs 🔗

@@ -123,7 +123,12 @@ impl TestServer {
         let client_a = server.create_client(cx_a, "user_a").await;
         let client_b = server.create_client(cx_b, "user_b").await;
         let channel_id = server
-            .make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
+            .make_channel(
+                "test-channel",
+                None,
+                (&client_a, cx_a),
+                &mut [(&client_b, cx_b)],
+            )
             .await;
         cx_a.run_until_parked();
 

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -562,14 +562,23 @@ impl CollabTitlebarItem {
     }
 
     fn window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
-        let project = if cx.is_window_active() {
-            Some(self.project.clone())
-        } else {
-            None
-        };
-        ActiveCall::global(cx)
-            .update(cx, |call, cx| call.set_location(project.as_ref(), cx))
-            .detach_and_log_err(cx);
+        if cx.is_window_active() {
+            ActiveCall::global(cx)
+                .update(cx, |call, cx| call.set_location(Some(&self.project), cx))
+                .detach_and_log_err(cx);
+            return;
+        }
+
+        if cx.active_window().is_none() {
+            ActiveCall::global(cx)
+                .update(cx, |call, cx| call.set_location(None, cx))
+                .detach_and_log_err(cx);
+        }
+        self.workspace
+            .update(cx, |workspace, cx| {
+                workspace.update_active_view_for_followers(cx);
+            })
+            .ok();
     }
 
     fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {

crates/workspace/src/workspace.rs 🔗

@@ -2910,25 +2910,27 @@ impl Workspace {
         Ok(())
     }
 
-    fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
+    pub fn update_active_view_for_followers(&mut self, cx: &mut WindowContext) {
         let mut is_project_item = true;
         let mut update = proto::UpdateActiveView::default();
 
-        if let Some(item) = self.active_item(cx) {
-            if item.focus_handle(cx).contains_focused(cx) {
-                if let Some(item) = item.to_followable_item_handle(cx) {
-                    is_project_item = item.is_project_item(cx);
-                    update = proto::UpdateActiveView {
-                        id: item
-                            .remote_id(&self.app_state.client, cx)
-                            .map(|id| id.to_proto()),
-                        leader_id: self.leader_for_pane(&self.active_pane),
-                    };
+        if cx.is_window_active() {
+            if let Some(item) = self.active_item(cx) {
+                if item.focus_handle(cx).contains_focused(cx) {
+                    if let Some(item) = item.to_followable_item_handle(cx) {
+                        is_project_item = item.is_project_item(cx);
+                        update = proto::UpdateActiveView {
+                            id: item
+                                .remote_id(&self.app_state.client, cx)
+                                .map(|id| id.to_proto()),
+                            leader_id: self.leader_for_pane(&self.active_pane),
+                        };
+                    }
                 }
             }
         }
 
-        if update.id != self.last_active_view_id {
+        if &update.id != &self.last_active_view_id {
             self.last_active_view_id = update.id.clone();
             self.update_followers(
                 is_project_item,