More following tests

Conrad Irwin created

Change summary

crates/collab/src/tests/following_tests.rs | 1991 +++++++++++------------
crates/gpui/src/app/test_context.rs        |    4 
crates/gpui/src/platform/test/platform.rs  |    4 
crates/workspace/src/workspace.rs          |   10 
4 files changed, 966 insertions(+), 1,043 deletions(-)

Detailed changes

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

@@ -3,8 +3,8 @@ use call::ActiveCall;
 use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
 use editor::{Editor, ExcerptRange, MultiBuffer};
 use gpui::{
-    point, BackgroundExecutor, Context, TestAppContext, View, VisualContext, VisualTestContext,
-    WindowContext,
+    point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext,
+    VisualTestContext, WindowContext,
 };
 use live_kit_client::MacOSDisplay;
 use project::project_settings::ProjectSettings;
@@ -517,1130 +517,1051 @@ async fn test_basic_following(
     );
 }
 
-// #[gpui::test]
-// async fn test_following_tab_order(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
+#[gpui::test]
+async fn test_following_tab_order(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
 
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
 
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "1.txt": "one",
+                "2.txt": "two",
+                "3.txt": "three",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
 
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
 
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
 
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
+    let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
 
-//     let client_b_id = project_a.update(cx_a, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
+    let client_b_id = project_a.update(cx_a, |project, _| {
+        project.collaborators().values().next().unwrap().peer_id
+    });
 
-//     //Open 1, 3 in that order on client A
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
+    //Open 1, 3 in that order on client A
+    workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
+    workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "3.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
 
-//     let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
-//         pane.update(cx, |pane, cx| {
-//             pane.items()
-//                 .map(|item| {
-//                     item.project_path(cx)
-//                         .unwrap()
-//                         .path
-//                         .to_str()
-//                         .unwrap()
-//                         .to_owned()
-//                 })
-//                 .collect::<Vec<_>>()
-//         })
-//     };
+    let pane_paths = |pane: &View<workspace::Pane>, cx: &mut VisualTestContext| {
+        pane.update(cx, |pane, cx| {
+            pane.items()
+                .map(|item| {
+                    item.project_path(cx)
+                        .unwrap()
+                        .path
+                        .to_str()
+                        .unwrap()
+                        .to_owned()
+                })
+                .collect::<Vec<_>>()
+        })
+    };
 
-//     //Verify that the tabs opened in the order we expect
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
+    //Verify that the tabs opened in the order we expect
+    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
 
-//     //Follow client B as client A
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.follow(client_b_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
+    //Follow client B as client A
+    workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
+    executor.run_until_parked();
 
-//     //Open just 2 on client B
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
+    //Open just 2 on client B
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
+    executor.run_until_parked();
 
-//     // Verify that newly opened followed file is at the end
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
+    // Verify that newly opened followed file is at the end
+    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
 
-//     //Open just 1 on client B
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
-//     executor.run_until_parked();
+    //Open just 1 on client B
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
+    executor.run_until_parked();
 
-//     // Verify that following into 1 did not reorder
-//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
-// }
+    // Verify that following into 1 did not reorder
+    assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
+}
 
-// #[gpui::test(iterations = 10)]
-// async fn test_peers_following_each_other(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
+#[gpui::test(iterations = 10)]
+async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
+    let executor = cx_a.executor();
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
 
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
 
-//     // Client A shares a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//                 "4.txt": "four",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
+    // Client A shares a project.
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "1.txt": "one",
+                "2.txt": "two",
+                "3.txt": "three",
+                "4.txt": "four",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
 
-//     // Client B joins the project.
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
+    // Client B joins the project.
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
 
-//     // Client A opens a file.
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
+    // Client A opens a file.
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
 
-//     // Client B opens a different file.
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
+    // Client B opens a different file.
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
 
-//     // Clients A and B follow each other in split panes
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-//     });
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
-//     });
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
+    // Clients A and B follow each other in split panes
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
+    });
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.follow(client_b.peer_id().unwrap(), cx)
+    });
+    executor.run_until_parked();
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
+    });
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.follow(client_a.peer_id().unwrap(), cx)
+    });
+    executor.run_until_parked();
 
-//     // Clients A and B return focus to the original files they had open
-//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
+    // Clients A and B return focus to the original files they had open
+    workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
+    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+    executor.run_until_parked();
 
-//     // Both clients see the other client's focused file in their right pane.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "1.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_b.peer_id(),
-//                 items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "2.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
-//             },
-//         ]
-//     );
-
-//     // Clients A and B each open a new file.
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "4.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
+    // Both clients see the other client's focused file in their right pane.
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(true, "1.txt".into())]
+            },
+            PaneSummary {
+                active: false,
+                leader: client_b.peer_id(),
+                items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(true, "2.txt".into())]
+            },
+            PaneSummary {
+                active: false,
+                leader: client_a.peer_id(),
+                items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
+            },
+        ]
+    );
 
-//     // Both client's see the other client open the new file, but keep their
-//     // focus on their own active pane.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (true, "3.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
+    // Clients A and B each open a new file.
+    workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "3.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
 
-//     // Client A focuses their right pane, in which they're following client B.
-//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "4.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
+    executor.run_until_parked();
 
-//     // Client B sees that client A is now looking at the same file as them.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: false,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (false, "3.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
+    // Both client's see the other client open the new file, but keep their
+    // focus on their own active pane.
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: false,
+                leader: client_b.peer_id(),
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (true, "4.txt".into())
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: false,
+                leader: client_a.peer_id(),
+                items: vec![
+                    (false, "2.txt".into()),
+                    (false, "1.txt".into()),
+                    (true, "3.txt".into())
+                ]
+            },
+        ]
+    );
 
-//     // Client B focuses their right pane, in which they're following client A,
-//     // who is following them.
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     executor.run_until_parked();
+    // Client A focuses their right pane, in which they're following client B.
+    workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
+    executor.run_until_parked();
 
-//     // Client A sees that client B is now looking at the same file as them.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (false, "3.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
+    // Client B sees that client A is now looking at the same file as them.
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_b.peer_id(),
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (true, "4.txt".into())
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: false,
+                leader: client_a.peer_id(),
+                items: vec![
+                    (false, "2.txt".into()),
+                    (false, "1.txt".into()),
+                    (false, "3.txt".into()),
+                    (true, "4.txt".into())
+                ]
+            },
+        ]
+    );
 
-//     // Client B focuses a file that they previously followed A to, breaking
-//     // the follow.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
+    // Client B focuses their right pane, in which they're following client A,
+    // who is following them.
+    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+    executor.run_until_parked();
 
-//     // Both clients see that client B is looking at that previous file.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "2.txt".into()),
-//                     (false, "1.txt".into()),
-//                     (true, "3.txt".into()),
-//                     (false, "4.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
+    // Client A sees that client B is now looking at the same file as them.
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_a.peer_id(),
+                items: vec![
+                    (false, "2.txt".into()),
+                    (false, "1.txt".into()),
+                    (false, "3.txt".into()),
+                    (true, "4.txt".into())
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_b.peer_id(),
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (true, "4.txt".into())
+                ]
+            },
+        ]
+    );
 
-//     // Client B closes tabs, some of which were originally opened by client A,
-//     // and some of which were originally opened by client B.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.close_inactive_items(&Default::default(), cx)
-//                 .unwrap()
-//                 .detach();
-//         });
-//     });
+    // Client B focuses a file that they previously followed A to, breaking
+    // the follow.
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.activate_prev_item(true, cx);
+        });
+    });
+    executor.run_until_parked();
 
-//     executor.run_until_parked();
+    // Both clients see that client B is looking at that previous file.
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![
+                    (false, "2.txt".into()),
+                    (false, "1.txt".into()),
+                    (true, "3.txt".into()),
+                    (false, "4.txt".into())
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_b.peer_id(),
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (false, "4.txt".into()),
+                    (true, "3.txt".into()),
+                ]
+            },
+        ]
+    );
 
-//     // Both clients see that Client B is looking at the previous tab.
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![(true, "3.txt".into()),]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_b.peer_id(),
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
+    // Client B closes tabs, some of which were originally opened by client A,
+    // and some of which were originally opened by client B.
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.close_inactive_items(&Default::default(), cx)
+                .unwrap()
+                .detach();
+        });
+    });
 
-//     // Client B follows client A again.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
+    executor.run_until_parked();
 
-//     // Client A cycles through some tabs.
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
+    // Both clients see that Client B is looking at the previous tab.
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![(true, "3.txt".into()),]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_b.peer_id(),
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (false, "4.txt".into()),
+                    (true, "3.txt".into()),
+                ]
+            },
+        ]
+    );
 
-//     // Client B follows client A into those tabs.
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
-//             },
-//         ]
-//     );
+    // Client B follows client A again.
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.follow(client_a.peer_id().unwrap(), cx)
+    });
+    executor.run_until_parked();
+    // Client A cycles through some tabs.
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.activate_prev_item(true, cx);
+        });
+    });
+    executor.run_until_parked();
 
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
+    // Client B follows client A into those tabs.
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![
+                    (false, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (true, "4.txt".into()),
+                    (false, "3.txt".into()),
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_a.peer_id(),
+                items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
+            },
+        ]
+    );
 
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (false, "1.txt".into()),
-//                     (true, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "3.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (true, "2.txt".into())
-//                 ]
-//             },
-//         ]
-//     );
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.activate_prev_item(true, cx);
+        });
+    });
+    executor.run_until_parked();
 
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.active_pane().update(cx, |pane, cx| {
-//             pane.activate_prev_item(true, cx);
-//         });
-//     });
-//     executor.run_until_parked();
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![
+                    (false, "1.txt".into()),
+                    (true, "2.txt".into()),
+                    (false, "4.txt".into()),
+                    (false, "3.txt".into()),
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_a.peer_id(),
+                items: vec![
+                    (false, "3.txt".into()),
+                    (false, "4.txt".into()),
+                    (true, "2.txt".into())
+                ]
+            },
+        ]
+    );
 
-//     assert_eq!(
-//         pane_summaries(&workspace_a, cx_a),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: None,
-//                 items: vec![
-//                     (true, "1.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "3.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-//     assert_eq!(
-//         pane_summaries(&workspace_b, cx_b),
-//         &[
-//             PaneSummary {
-//                 active: false,
-//                 leader: None,
-//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
-//             },
-//             PaneSummary {
-//                 active: true,
-//                 leader: client_a.peer_id(),
-//                 items: vec![
-//                     (false, "3.txt".into()),
-//                     (false, "4.txt".into()),
-//                     (false, "2.txt".into()),
-//                     (true, "1.txt".into()),
-//                 ]
-//             },
-//         ]
-//     );
-// }
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.active_pane().update(cx, |pane, cx| {
+            pane.activate_prev_item(true, cx);
+        });
+    });
+    executor.run_until_parked();
 
-// #[gpui::test(iterations = 10)]
-// async fn test_auto_unfollowing(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     // 2 clients connect to a server.
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
+    assert_eq!(
+        pane_summaries(&workspace_a, cx_a),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: None,
+                items: vec![
+                    (true, "1.txt".into()),
+                    (false, "2.txt".into()),
+                    (false, "4.txt".into()),
+                    (false, "3.txt".into()),
+                ]
+            },
+        ]
+    );
+    assert_eq!(
+        pane_summaries(&workspace_b, cx_b),
+        &[
+            PaneSummary {
+                active: false,
+                leader: None,
+                items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
+            },
+            PaneSummary {
+                active: true,
+                leader: client_a.peer_id(),
+                items: vec![
+                    (false, "3.txt".into()),
+                    (false, "4.txt".into()),
+                    (false, "2.txt".into()),
+                    (true, "1.txt".into()),
+                ]
+            },
+        ]
+    );
+}
 
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
+#[gpui::test(iterations = 10)]
+async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
+    // 2 clients connect to a server.
+    let executor = cx_a.executor();
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
 
-//     // Client A shares a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "1.txt": "one",
-//                 "2.txt": "two",
-//                 "3.txt": "three",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
 
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
+    // Client A shares a project.
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "1.txt": "one",
+                "2.txt": "two",
+                "3.txt": "three",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
 
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
 
-//     // Client A opens some editors.
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let _editor_a1 = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 
-//     // Client B starts following client A.
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
-//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
-//     let leader_id = project_b.update(cx_b, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
-//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .active_item(cx)
-//             .unwrap()
-//             .downcast::<Editor>()
-//             .unwrap()
-//     });
+    let _editor_a1 = workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "1.txt"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
 
-//     // When client B moves, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
+    // Client B starts following client A.
+    let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
+    let leader_id = project_b.update(cx_b, |project, _| {
+        project.collaborators().values().next().unwrap().peer_id
+    });
+    workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
+    executor.run_until_parked();
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
+    let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
+        workspace
+            .active_item(cx)
+            .unwrap()
+            .downcast::<Editor>()
+            .unwrap()
+    });
 
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
+    // When client B moves, it automatically stops following client A.
+    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        None
+    );
 
-//     // When client B edits, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
+    workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
+    executor.run_until_parked();
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
 
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
+    // When client B edits, it automatically stops following client A.
+    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        None
+    );
 
-//     // When client B scrolls, it automatically stops following client A.
-//     editor_b2.update(cx_b, |editor, cx| {
-//         editor.set_scroll_position(point(0., 3.), cx)
-//     });
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
+    workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
+    executor.run_until_parked();
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
 
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.follow(leader_id, cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
+    // When client B scrolls, it automatically stops following client A.
+    editor_b2.update(cx_b, |editor, cx| {
+        editor.set_scroll_position(point(0., 3.), cx)
+    });
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        None
+    );
 
-//     // When client B activates a different pane, it continues following client A in the original pane.
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
-//     });
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
+    workspace_b.update(cx_b, |workspace, cx| workspace.follow(leader_id, cx));
+    executor.run_until_parked();
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
 
-//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         Some(leader_id)
-//     );
+    // When client B activates a different pane, it continues following client A in the original pane.
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
+    });
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
 
-//     // When client B activates a different item in the original pane, it automatically stops following client A.
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
-//     assert_eq!(
-//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
-//         None
-//     );
-// }
+    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        Some(leader_id)
+    );
 
-// #[gpui::test(iterations = 10)]
-// async fn test_peers_simultaneously_following_each_other(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
+    // When client B activates a different item in the original pane, it automatically stops following client A.
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "2.txt"), None, true, cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(
+        workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+        None
+    );
+}
 
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
+#[gpui::test(iterations = 10)]
+async fn test_peers_simultaneously_following_each_other(
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let executor = cx_a.executor();
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
 
-//     client_a.fs().insert_tree("/a", json!({})).await;
-//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
 
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
+    client_a.fs().insert_tree("/a", json!({})).await;
+    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
 
-//     executor.run_until_parked();
-//     let client_a_id = project_b.update(cx_b, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
-//     let client_b_id = project_a.update(cx_a, |project, _| {
-//         project.collaborators().values().next().unwrap().peer_id
-//     });
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 
-//     let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
-//         workspace.follow(client_b_id, cx).unwrap()
-//     });
-//     let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
-//         workspace.follow(client_a_id, cx).unwrap()
-//     });
+    executor.run_until_parked();
+    let client_a_id = project_b.update(cx_b, |project, _| {
+        project.collaborators().values().next().unwrap().peer_id
+    });
+    let client_b_id = project_a.update(cx_a, |project, _| {
+        project.collaborators().values().next().unwrap().peer_id
+    });
 
-//     futures::try_join!(a_follow_b, b_follow_a).unwrap();
-//     workspace_a.update(cx_a, |workspace, _| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_b_id)
-//         );
-//     });
-//     workspace_b.update(cx_b, |workspace, _| {
-//         assert_eq!(
-//             workspace.leader_for_pane(workspace.active_pane()),
-//             Some(client_a_id)
-//         );
-//     });
-// }
+    workspace_a.update(cx_a, |workspace, cx| workspace.follow(client_b_id, cx));
+    workspace_b.update(cx_b, |workspace, cx| workspace.follow(client_a_id, cx));
+    executor.run_until_parked();
 
-// #[gpui::test(iterations = 10)]
-// async fn test_following_across_workspaces(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     // a and b join a channel/call
-//     // a shares project 1
-//     // b shares project 2
-//     //
-//     // b follows a: causes project 2 to be joined, and b to follow a.
-//     // b opens a different file in project 2, a follows b
-//     // b opens a different file in project 1, a cannot follow b
-//     // b shares the project, a joins the project and follows b
-//     let mut server = TestServer::start(executor.clone()).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
+    workspace_a.update(cx_a, |workspace, _| {
+        assert_eq!(
+            workspace.leader_for_pane(workspace.active_pane()),
+            Some(client_b_id)
+        );
+    });
+    workspace_b.update(cx_b, |workspace, _| {
+        assert_eq!(
+            workspace.leader_for_pane(workspace.active_pane()),
+            Some(client_a_id)
+        );
+    });
+}
 
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "w.rs": "",
-//                 "x.rs": "",
-//             }),
-//         )
-//         .await;
+#[gpui::test(iterations = 10)]
+async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
+    // a and b join a channel/call
+    // a shares project 1
+    // b shares project 2
+    //
+    // b follows a: causes project 2 to be joined, and b to follow a.
+    // b opens a different file in project 2, a follows b
+    // b opens a different file in project 1, a cannot follow b
+    // b shares the project, a joins the project and follows b
+    let executor = cx_a.executor();
+    let mut server = TestServer::start(executor.clone()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
 
-//     client_b
-//         .fs()
-//         .insert_tree(
-//             "/b",
-//             json!({
-//                 "y.rs": "",
-//                 "z.rs": "",
-//             }),
-//         )
-//         .await;
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "w.rs": "",
+                "x.rs": "",
+            }),
+        )
+        .await;
 
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
+    client_b
+        .fs()
+        .insert_tree(
+            "/b",
+            json!({
+                "y.rs": "",
+                "z.rs": "",
+            }),
+        )
+        .await;
 
-//     let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
-//     let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
 
-//     let workspace_a = client_a
-//         .build_workspace(&project_a, cx_a)
-//         .root(cx_a)
-//         .unwrap();
-//     let workspace_b = client_b
-//         .build_workspace(&project_b, cx_b)
-//         .root(cx_b)
-//         .unwrap();
+    let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
+    let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
 
-//     cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
-//     cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
+    let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);
+    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
 
-//     active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
+    cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
+    cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
 
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
+    active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
 
-//     todo!("could be wrong")
-//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
-//     let cx_a = &mut cx_a;
-//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
-//     let cx_b = &mut cx_b;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
 
-//     workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
+    workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
+        })
+        .await
+        .unwrap();
 
-//     executor.run_until_parked();
-//     assert_eq!(visible_push_notifications(cx_b).len(), 1);
+    executor.run_until_parked();
+    assert_eq!(visible_push_notifications(cx_b).len(), 1);
 
-//     workspace_b.update(cx_b, |workspace, cx| {
-//         workspace
-//             .follow(client_a.peer_id().unwrap(), cx)
-//             .unwrap()
-//             .detach()
-//     });
+    workspace_b.update(cx_b, |workspace, cx| {
+        workspace.follow(client_a.peer_id().unwrap(), cx)
+    });
 
-//     executor.run_until_parked();
-//     let workspace_b_project_a = cx_b
-//         .windows()
-//         .iter()
-//         .max_by_key(|window| window.item_id())
-//         .unwrap()
-//         .downcast::<Workspace>()
-//         .unwrap()
-//         .root(cx_b)
-//         .unwrap();
+    executor.run_until_parked();
+    let workspace_b_project_a = cx_b
+        .windows()
+        .iter()
+        .max_by_key(|window| window.window_id())
+        .unwrap()
+        .downcast::<Workspace>()
+        .unwrap()
+        .root(cx_b)
+        .unwrap();
 
-//     // assert that b is following a in project a in w.rs
-//     workspace_b_project_a.update(cx_b, |workspace, cx| {
-//         assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
-//         assert_eq!(
-//             client_a.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
-//     });
+    // assert that b is following a in project a in w.rs
+    workspace_b_project_a.update(cx_b, |workspace, cx| {
+        assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
+        assert_eq!(
+            client_a.peer_id(),
+            workspace.leader_for_pane(workspace.active_pane())
+        );
+        let item = workspace.active_item(cx).unwrap();
+        assert_eq!(
+            item.tab_description(0, cx).unwrap(),
+            SharedString::from("w.rs")
+        );
+    });
 
-//     // TODO: in app code, this would be done by the collab_ui.
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             let project = workspace_b_project_a.read(cx).project().clone();
-//             call.set_location(Some(&project), cx)
-//         })
-//         .await
-//         .unwrap();
+    // TODO: in app code, this would be done by the collab_ui.
+    active_call_b
+        .update(cx_b, |call, cx| {
+            let project = workspace_b_project_a.read(cx).project().clone();
+            call.set_location(Some(&project), cx)
+        })
+        .await
+        .unwrap();
 
-//     // assert that there are no share notifications open
-//     assert_eq!(visible_push_notifications(cx_b).len(), 0);
+    // assert that there are no share notifications open
+    assert_eq!(visible_push_notifications(cx_b).len(), 0);
 
-//     // b moves to x.rs in a's project, and a follows
-//     workspace_b_project_a
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
+    // b moves to x.rs in a's project, and a follows
+    workspace_b_project_a
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
+        })
+        .await
+        .unwrap();
 
-//     executor.run_until_parked();
-//     workspace_b_project_a.update(cx_b, |workspace, cx| {
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
-//     });
+    executor.run_until_parked();
+    workspace_b_project_a.update(cx_b, |workspace, cx| {
+        let item = workspace.active_item(cx).unwrap();
+        assert_eq!(
+            item.tab_description(0, cx).unwrap(),
+            SharedString::from("x.rs")
+        );
+    });
 
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         workspace
-//             .follow(client_b.peer_id().unwrap(), cx)
-//             .unwrap()
-//             .detach()
-//     });
+    workspace_a.update(cx_a, |workspace, cx| {
+        workspace.follow(client_b.peer_id().unwrap(), cx)
+    });
 
-//     executor.run_until_parked();
-//     workspace_a.update(cx_a, |workspace, cx| {
-//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-//         assert_eq!(
-//             client_b.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_pane().read(cx).active_item().unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
-//     });
+    executor.run_until_parked();
+    workspace_a.update(cx_a, |workspace, cx| {
+        assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
+        assert_eq!(
+            client_b.peer_id(),
+            workspace.leader_for_pane(workspace.active_pane())
+        );
+        let item = workspace.active_pane().read(cx).active_item().unwrap();
+        assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs");
+    });
 
-//     // b moves to y.rs in b's project, a is still following but can't yet see
-//     workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap();
+    // b moves to y.rs in b's project, a is still following but can't yet see
+    workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
+        })
+        .await
+        .unwrap();
 
-//     // TODO: in app code, this would be done by the collab_ui.
-//     active_call_b
-//         .update(cx_b, |call, cx| {
-//             let project = workspace_b.read(cx).project().clone();
-//             call.set_location(Some(&project), cx)
-//         })
-//         .await
-//         .unwrap();
+    // TODO: in app code, this would be done by the collab_ui.
+    active_call_b
+        .update(cx_b, |call, cx| {
+            let project = workspace_b.read(cx).project().clone();
+            call.set_location(Some(&project), cx)
+        })
+        .await
+        .unwrap();
 
-//     let project_b_id = active_call_b
-//         .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
-//         .await
-//         .unwrap();
+    let project_b_id = active_call_b
+        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
+        .await
+        .unwrap();
 
-//     executor.run_until_parked();
-//     assert_eq!(visible_push_notifications(cx_a).len(), 1);
-//     cx_a.update(|cx| {
-//         workspace::join_remote_project(
-//             project_b_id,
-//             client_b.user_id().unwrap(),
-//             client_a.app_state.clone(),
-//             cx,
-//         )
-//     })
-//     .await
-//     .unwrap();
+    executor.run_until_parked();
+    assert_eq!(visible_push_notifications(cx_a).len(), 1);
+    cx_a.update(|cx| {
+        workspace::join_remote_project(
+            project_b_id,
+            client_b.user_id().unwrap(),
+            client_a.app_state.clone(),
+            cx,
+        )
+    })
+    .await
+    .unwrap();
 
-//     executor.run_until_parked();
+    executor.run_until_parked();
 
-//     assert_eq!(visible_push_notifications(cx_a).len(), 0);
-//     let workspace_a_project_b = cx_a
-//         .windows()
-//         .iter()
-//         .max_by_key(|window| window.item_id())
-//         .unwrap()
-//         .downcast::<Workspace>()
-//         .unwrap()
-//         .root(cx_a)
-//         .unwrap();
+    assert_eq!(visible_push_notifications(cx_a).len(), 0);
+    let workspace_a_project_b = cx_a
+        .windows()
+        .iter()
+        .max_by_key(|window| window.window_id())
+        .unwrap()
+        .downcast::<Workspace>()
+        .unwrap()
+        .root(cx_a)
+        .unwrap();
 
-//     workspace_a_project_b.update(cx_a, |workspace, cx| {
-//         assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
-//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
-//         assert_eq!(
-//             client_b.peer_id(),
-//             workspace.leader_for_pane(workspace.active_pane())
-//         );
-//         let item = workspace.active_item(cx).unwrap();
-//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
-//     });
-// }
+    workspace_a_project_b.update(cx_a, |workspace, cx| {
+        assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
+        assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
+        assert_eq!(
+            client_b.peer_id(),
+            workspace.leader_for_pane(workspace.active_pane())
+        );
+        let item = workspace.active_item(cx).unwrap();
+        assert_eq!(
+            item.tab_description(0, cx).unwrap(),
+            SharedString::from("y.rs")
+        );
+    });
+}
 
 // #[gpui::test]
 // async fn test_following_into_excluded_file(

crates/gpui/src/app/test_context.rs 🔗

@@ -223,6 +223,10 @@ impl TestAppContext {
             .unwrap();
     }
 
+    pub fn windows(&self) -> Vec<AnyWindowHandle> {
+        self.app.borrow().windows().clone()
+    }
+
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,

crates/gpui/src/platform/test/platform.rs 🔗

@@ -105,9 +105,7 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn activate(&self, _ignoring_other_apps: bool) {
-        unimplemented!()
-    }
+    fn activate(&self, _ignoring_other_apps: bool) {}
 
     fn hide(&self) {
         unimplemented!()

crates/workspace/src/workspace.rs 🔗

@@ -2451,11 +2451,11 @@ impl Workspace {
         Some(leader_id)
     }
 
-    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
-    //         self.follower_states
-    //             .values()
-    //             .any(|state| state.leader_id == peer_id)
-    //     }
+    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
+        self.follower_states
+            .values()
+            .any(|state| state.leader_id == peer_id)
+    }
 
     fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
         let active_entry = self.active_project_path(cx);