following_tests.rs

   1//todo!(workspace)
   2
   3// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
   4// use call::ActiveCall;
   5// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
   6// use editor::{Editor, ExcerptRange, MultiBuffer};
   7// use gpui::{point, BackgroundExecutor, TestAppContext, View, VisualTestContext, WindowContext};
   8// use live_kit_client::MacOSDisplay;
   9// use project::project_settings::ProjectSettings;
  10// use rpc::proto::PeerId;
  11// use serde_json::json;
  12// use settings::SettingsStore;
  13// use std::borrow::Cow;
  14// use workspace::{
  15//     dock::{test::TestPanel, DockPosition},
  16//     item::{test::TestItem, ItemHandle as _},
  17//     shared_screen::SharedScreen,
  18//     SplitDirection, Workspace,
  19// };
  20
  21// #[gpui::test(iterations = 10)]
  22// async fn test_basic_following(
  23//     executor: BackgroundExecutor,
  24//     cx_a: &mut TestAppContext,
  25//     cx_b: &mut TestAppContext,
  26//     cx_c: &mut TestAppContext,
  27//     cx_d: &mut TestAppContext,
  28// ) {
  29//     let mut server = TestServer::start(executor.clone()).await;
  30//     let client_a = server.create_client(cx_a, "user_a").await;
  31//     let client_b = server.create_client(cx_b, "user_b").await;
  32//     let client_c = server.create_client(cx_c, "user_c").await;
  33//     let client_d = server.create_client(cx_d, "user_d").await;
  34//     server
  35//         .create_room(&mut [
  36//             (&client_a, cx_a),
  37//             (&client_b, cx_b),
  38//             (&client_c, cx_c),
  39//             (&client_d, cx_d),
  40//         ])
  41//         .await;
  42//     let active_call_a = cx_a.read(ActiveCall::global);
  43//     let active_call_b = cx_b.read(ActiveCall::global);
  44
  45//     cx_a.update(editor::init);
  46//     cx_b.update(editor::init);
  47
  48//     client_a
  49//         .fs()
  50//         .insert_tree(
  51//             "/a",
  52//             json!({
  53//                 "1.txt": "one\none\none",
  54//                 "2.txt": "two\ntwo\ntwo",
  55//                 "3.txt": "three\nthree\nthree",
  56//             }),
  57//         )
  58//         .await;
  59//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  60//     active_call_a
  61//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
  62//         .await
  63//         .unwrap();
  64
  65//     let project_id = active_call_a
  66//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  67//         .await
  68//         .unwrap();
  69//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
  70//     active_call_b
  71//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
  72//         .await
  73//         .unwrap();
  74
  75//     let window_a = client_a.build_workspace(&project_a, cx_a);
  76//     let workspace_a = window_a.root(cx_a).unwrap();
  77//     let window_b = client_b.build_workspace(&project_b, cx_b);
  78//     let workspace_b = window_b.root(cx_b).unwrap();
  79
  80//     todo!("could be wrong")
  81//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
  82//     let cx_a = &mut cx_a;
  83//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
  84//     let cx_b = &mut cx_b;
  85//     let mut cx_c = VisualTestContext::from_window(*window_c, cx_c);
  86//     let cx_c = &mut cx_c;
  87//     let mut cx_d = VisualTestContext::from_window(*window_d, cx_d);
  88//     let cx_d = &mut cx_d;
  89
  90//     // Client A opens some editors.
  91//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
  92//     let editor_a1 = workspace_a
  93//         .update(cx_a, |workspace, cx| {
  94//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
  95//         })
  96//         .await
  97//         .unwrap()
  98//         .downcast::<Editor>()
  99//         .unwrap();
 100//     let editor_a2 = workspace_a
 101//         .update(cx_a, |workspace, cx| {
 102//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
 103//         })
 104//         .await
 105//         .unwrap()
 106//         .downcast::<Editor>()
 107//         .unwrap();
 108
 109//     // Client B opens an editor.
 110//     let editor_b1 = workspace_b
 111//         .update(cx_b, |workspace, cx| {
 112//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
 113//         })
 114//         .await
 115//         .unwrap()
 116//         .downcast::<Editor>()
 117//         .unwrap();
 118
 119//     let peer_id_a = client_a.peer_id().unwrap();
 120//     let peer_id_b = client_b.peer_id().unwrap();
 121//     let peer_id_c = client_c.peer_id().unwrap();
 122//     let peer_id_d = client_d.peer_id().unwrap();
 123
 124//     // Client A updates their selections in those editors
 125//     editor_a1.update(cx_a, |editor, cx| {
 126//         editor.handle_input("a", cx);
 127//         editor.handle_input("b", cx);
 128//         editor.handle_input("c", cx);
 129//         editor.select_left(&Default::default(), cx);
 130//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
 131//     });
 132//     editor_a2.update(cx_a, |editor, cx| {
 133//         editor.handle_input("d", cx);
 134//         editor.handle_input("e", cx);
 135//         editor.select_left(&Default::default(), cx);
 136//         assert_eq!(editor.selections.ranges(cx), vec![2..1]);
 137//     });
 138
 139//     // When client B starts following client A, all visible view states are replicated to client B.
 140//     workspace_b
 141//         .update(cx_b, |workspace, cx| {
 142//             workspace.follow(peer_id_a, cx).unwrap()
 143//         })
 144//         .await
 145//         .unwrap();
 146
 147//     cx_c.executor().run_until_parked();
 148//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
 149//         workspace
 150//             .active_item(cx)
 151//             .unwrap()
 152//             .downcast::<Editor>()
 153//             .unwrap()
 154//     });
 155//     assert_eq!(
 156//         cx_b.read(|cx| editor_b2.project_path(cx)),
 157//         Some((worktree_id, "2.txt").into())
 158//     );
 159//     assert_eq!(
 160//         editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
 161//         vec![2..1]
 162//     );
 163//     assert_eq!(
 164//         editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
 165//         vec![3..2]
 166//     );
 167
 168//     cx_c.executor().run_until_parked();
 169//     let active_call_c = cx_c.read(ActiveCall::global);
 170//     let project_c = client_c.build_remote_project(project_id, cx_c).await;
 171//     let window_c = client_c.build_workspace(&project_c, cx_c);
 172//     let workspace_c = window_c.root(cx_c).unwrap();
 173//     active_call_c
 174//         .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
 175//         .await
 176//         .unwrap();
 177//     drop(project_c);
 178
 179//     // Client C also follows client A.
 180//     workspace_c
 181//         .update(cx_c, |workspace, cx| {
 182//             workspace.follow(peer_id_a, cx).unwrap()
 183//         })
 184//         .await
 185//         .unwrap();
 186
 187//     cx_d.executor().run_until_parked();
 188//     let active_call_d = cx_d.read(ActiveCall::global);
 189//     let project_d = client_d.build_remote_project(project_id, cx_d).await;
 190//     let workspace_d = client_d
 191//         .build_workspace(&project_d, cx_d)
 192//         .root(cx_d)
 193//         .unwrap();
 194//     active_call_d
 195//         .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
 196//         .await
 197//         .unwrap();
 198//     drop(project_d);
 199
 200//     // All clients see that clients B and C are following client A.
 201//     cx_c.executor().run_until_parked();
 202//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
 203//         assert_eq!(
 204//             followers_by_leader(project_id, cx),
 205//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
 206//             "followers seen by {name}"
 207//         );
 208//     }
 209
 210//     // Client C unfollows client A.
 211//     workspace_c.update(cx_c, |workspace, cx| {
 212//         workspace.unfollow(&workspace.active_pane().clone(), cx);
 213//     });
 214
 215//     // All clients see that clients B is following client A.
 216//     cx_c.executor().run_until_parked();
 217//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
 218//         assert_eq!(
 219//             followers_by_leader(project_id, cx),
 220//             &[(peer_id_a, vec![peer_id_b])],
 221//             "followers seen by {name}"
 222//         );
 223//     }
 224
 225//     // Client C re-follows client A.
 226//     workspace_c
 227//         .update(cx_c, |workspace, cx| {
 228//             workspace.follow(peer_id_a, cx).unwrap()
 229//         })
 230//         .await
 231//         .unwrap();
 232
 233//     // All clients see that clients B and C are following client A.
 234//     cx_c.executor().run_until_parked();
 235//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
 236//         assert_eq!(
 237//             followers_by_leader(project_id, cx),
 238//             &[(peer_id_a, vec![peer_id_b, peer_id_c])],
 239//             "followers seen by {name}"
 240//         );
 241//     }
 242
 243//     // Client D follows client B, then switches to following client C.
 244//     workspace_d
 245//         .update(cx_d, |workspace, cx| {
 246//             workspace.follow(peer_id_b, cx).unwrap()
 247//         })
 248//         .await
 249//         .unwrap();
 250//     workspace_d
 251//         .update(cx_d, |workspace, cx| {
 252//             workspace.follow(peer_id_c, cx).unwrap()
 253//         })
 254//         .await
 255//         .unwrap();
 256
 257//     // All clients see that D is following C
 258//     cx_d.executor().run_until_parked();
 259//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
 260//         assert_eq!(
 261//             followers_by_leader(project_id, cx),
 262//             &[
 263//                 (peer_id_a, vec![peer_id_b, peer_id_c]),
 264//                 (peer_id_c, vec![peer_id_d])
 265//             ],
 266//             "followers seen by {name}"
 267//         );
 268//     }
 269
 270//     // Client C closes the project.
 271//     window_c.remove(cx_c);
 272//     cx_c.drop_last(workspace_c);
 273
 274//     // Clients A and B see that client B is following A, and client C is not present in the followers.
 275//     cx_c.executor().run_until_parked();
 276//     for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
 277//         assert_eq!(
 278//             followers_by_leader(project_id, cx),
 279//             &[(peer_id_a, vec![peer_id_b]),],
 280//             "followers seen by {name}"
 281//         );
 282//     }
 283
 284//     // When client A activates a different editor, client B does so as well.
 285//     workspace_a.update(cx_a, |workspace, cx| {
 286//         workspace.activate_item(&editor_a1, cx)
 287//     });
 288//     executor.run_until_parked();
 289//     workspace_b.update(cx_b, |workspace, cx| {
 290//         assert_eq!(
 291//             workspace.active_item(cx).unwrap().item_id(),
 292//             editor_b1.item_id()
 293//         );
 294//     });
 295
 296//     // When client A opens a multibuffer, client B does so as well.
 297//     let multibuffer_a = cx_a.build_model(|cx| {
 298//         let buffer_a1 = project_a.update(cx, |project, cx| {
 299//             project
 300//                 .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
 301//                 .unwrap()
 302//         });
 303//         let buffer_a2 = project_a.update(cx, |project, cx| {
 304//             project
 305//                 .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
 306//                 .unwrap()
 307//         });
 308//         let mut result = MultiBuffer::new(0);
 309//         result.push_excerpts(
 310//             buffer_a1,
 311//             [ExcerptRange {
 312//                 context: 0..3,
 313//                 primary: None,
 314//             }],
 315//             cx,
 316//         );
 317//         result.push_excerpts(
 318//             buffer_a2,
 319//             [ExcerptRange {
 320//                 context: 4..7,
 321//                 primary: None,
 322//             }],
 323//             cx,
 324//         );
 325//         result
 326//     });
 327//     let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
 328//         let editor =
 329//             cx.build_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
 330//         workspace.add_item(Box::new(editor.clone()), cx);
 331//         editor
 332//     });
 333//     executor.run_until_parked();
 334//     let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {
 335//         workspace
 336//             .active_item(cx)
 337//             .unwrap()
 338//             .downcast::<Editor>()
 339//             .unwrap()
 340//     });
 341//     assert_eq!(
 342//         multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),
 343//         multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),
 344//     );
 345
 346//     // When client A navigates back and forth, client B does so as well.
 347//     workspace_a
 348//         .update(cx_a, |workspace, cx| {
 349//             workspace.go_back(workspace.active_pane().downgrade(), cx)
 350//         })
 351//         .await
 352//         .unwrap();
 353//     executor.run_until_parked();
 354//     workspace_b.update(cx_b, |workspace, cx| {
 355//         assert_eq!(
 356//             workspace.active_item(cx).unwrap().item_id(),
 357//             editor_b1.item_id()
 358//         );
 359//     });
 360
 361//     workspace_a
 362//         .update(cx_a, |workspace, cx| {
 363//             workspace.go_back(workspace.active_pane().downgrade(), cx)
 364//         })
 365//         .await
 366//         .unwrap();
 367//     executor.run_until_parked();
 368//     workspace_b.update(cx_b, |workspace, cx| {
 369//         assert_eq!(
 370//             workspace.active_item(cx).unwrap().item_id(),
 371//             editor_b2.item_id()
 372//         );
 373//     });
 374
 375//     workspace_a
 376//         .update(cx_a, |workspace, cx| {
 377//             workspace.go_forward(workspace.active_pane().downgrade(), cx)
 378//         })
 379//         .await
 380//         .unwrap();
 381//     executor.run_until_parked();
 382//     workspace_b.update(cx_b, |workspace, cx| {
 383//         assert_eq!(
 384//             workspace.active_item(cx).unwrap().item_id(),
 385//             editor_b1.item_id()
 386//         );
 387//     });
 388
 389//     // Changes to client A's editor are reflected on client B.
 390//     editor_a1.update(cx_a, |editor, cx| {
 391//         editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
 392//     });
 393//     executor.run_until_parked();
 394//     editor_b1.update(cx_b, |editor, cx| {
 395//         assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
 396//     });
 397
 398//     editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
 399//     executor.run_until_parked();
 400//     editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
 401
 402//     editor_a1.update(cx_a, |editor, cx| {
 403//         editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
 404//         editor.set_scroll_position(point(0., 100.), cx);
 405//     });
 406//     executor.run_until_parked();
 407//     editor_b1.update(cx_b, |editor, cx| {
 408//         assert_eq!(editor.selections.ranges(cx), &[3..3]);
 409//     });
 410
 411//     // After unfollowing, client B stops receiving updates from client A.
 412//     workspace_b.update(cx_b, |workspace, cx| {
 413//         workspace.unfollow(&workspace.active_pane().clone(), cx)
 414//     });
 415//     workspace_a.update(cx_a, |workspace, cx| {
 416//         workspace.activate_item(&editor_a2, cx)
 417//     });
 418//     executor.run_until_parked();
 419//     assert_eq!(
 420//         workspace_b.update(cx_b, |workspace, cx| workspace
 421//             .active_item(cx)
 422//             .unwrap()
 423//             .item_id()),
 424//         editor_b1.item_id()
 425//     );
 426
 427//     // Client A starts following client B.
 428//     workspace_a
 429//         .update(cx_a, |workspace, cx| {
 430//             workspace.follow(peer_id_b, cx).unwrap()
 431//         })
 432//         .await
 433//         .unwrap();
 434//     assert_eq!(
 435//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
 436//         Some(peer_id_b)
 437//     );
 438//     assert_eq!(
 439//         workspace_a.update(cx_a, |workspace, cx| workspace
 440//             .active_item(cx)
 441//             .unwrap()
 442//             .item_id()),
 443//         editor_a1.item_id()
 444//     );
 445
 446//     // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
 447//     let display = MacOSDisplay::new();
 448//     active_call_b
 449//         .update(cx_b, |call, cx| call.set_location(None, cx))
 450//         .await
 451//         .unwrap();
 452//     active_call_b
 453//         .update(cx_b, |call, cx| {
 454//             call.room().unwrap().update(cx, |room, cx| {
 455//                 room.set_display_sources(vec![display.clone()]);
 456//                 room.share_screen(cx)
 457//             })
 458//         })
 459//         .await
 460//         .unwrap();
 461//     executor.run_until_parked();
 462//     let shared_screen = workspace_a.update(cx_a, |workspace, cx| {
 463//         workspace
 464//             .active_item(cx)
 465//             .expect("no active item")
 466//             .downcast::<SharedScreen>()
 467//             .expect("active item isn't a shared screen")
 468//     });
 469
 470//     // Client B activates Zed again, which causes the previous editor to become focused again.
 471//     active_call_b
 472//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
 473//         .await
 474//         .unwrap();
 475//     executor.run_until_parked();
 476//     workspace_a.update(cx_a, |workspace, cx| {
 477//         assert_eq!(
 478//             workspace.active_item(cx).unwrap().item_id(),
 479//             editor_a1.item_id()
 480//         )
 481//     });
 482
 483//     // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
 484//     workspace_b.update(cx_b, |workspace, cx| {
 485//         workspace.activate_item(&multibuffer_editor_b, cx)
 486//     });
 487//     executor.run_until_parked();
 488//     workspace_a.update(cx_a, |workspace, cx| {
 489//         assert_eq!(
 490//             workspace.active_item(cx).unwrap().item_id(),
 491//             multibuffer_editor_a.item_id()
 492//         )
 493//     });
 494
 495//     // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
 496//     let panel = window_b.build_view(cx_b, |_| TestPanel::new(DockPosition::Left));
 497//     workspace_b.update(cx_b, |workspace, cx| {
 498//         workspace.add_panel(panel, cx);
 499//         workspace.toggle_panel_focus::<TestPanel>(cx);
 500//     });
 501//     executor.run_until_parked();
 502//     assert_eq!(
 503//         workspace_a.update(cx_a, |workspace, cx| workspace
 504//             .active_item(cx)
 505//             .unwrap()
 506//             .item_id()),
 507//         shared_screen.item_id()
 508//     );
 509
 510//     // Toggling the focus back to the pane causes client A to return to the multibuffer.
 511//     workspace_b.update(cx_b, |workspace, cx| {
 512//         workspace.toggle_panel_focus::<TestPanel>(cx);
 513//     });
 514//     executor.run_until_parked();
 515//     workspace_a.update(cx_a, |workspace, cx| {
 516//         assert_eq!(
 517//             workspace.active_item(cx).unwrap().item_id(),
 518//             multibuffer_editor_a.item_id()
 519//         )
 520//     });
 521
 522//     // Client B activates an item that doesn't implement following,
 523//     // so the previously-opened screen-sharing item gets activated.
 524//     let unfollowable_item = window_b.build_view(cx_b, |_| TestItem::new());
 525//     workspace_b.update(cx_b, |workspace, cx| {
 526//         workspace.active_pane().update(cx, |pane, cx| {
 527//             pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
 528//         })
 529//     });
 530//     executor.run_until_parked();
 531//     assert_eq!(
 532//         workspace_a.update(cx_a, |workspace, cx| workspace
 533//             .active_item(cx)
 534//             .unwrap()
 535//             .item_id()),
 536//         shared_screen.item_id()
 537//     );
 538
 539//     // Following interrupts when client B disconnects.
 540//     client_b.disconnect(&cx_b.to_async());
 541//     executor.advance_clock(RECONNECT_TIMEOUT);
 542//     assert_eq!(
 543//         workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
 544//         None
 545//     );
 546// }
 547
 548// #[gpui::test]
 549// async fn test_following_tab_order(
 550//     executor: BackgroundExecutor,
 551//     cx_a: &mut TestAppContext,
 552//     cx_b: &mut TestAppContext,
 553// ) {
 554//     let mut server = TestServer::start(executor.clone()).await;
 555//     let client_a = server.create_client(cx_a, "user_a").await;
 556//     let client_b = server.create_client(cx_b, "user_b").await;
 557//     server
 558//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 559//         .await;
 560//     let active_call_a = cx_a.read(ActiveCall::global);
 561//     let active_call_b = cx_b.read(ActiveCall::global);
 562
 563//     cx_a.update(editor::init);
 564//     cx_b.update(editor::init);
 565
 566//     client_a
 567//         .fs()
 568//         .insert_tree(
 569//             "/a",
 570//             json!({
 571//                 "1.txt": "one",
 572//                 "2.txt": "two",
 573//                 "3.txt": "three",
 574//             }),
 575//         )
 576//         .await;
 577//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 578//     active_call_a
 579//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
 580//         .await
 581//         .unwrap();
 582
 583//     let project_id = active_call_a
 584//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 585//         .await
 586//         .unwrap();
 587//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 588//     active_call_b
 589//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
 590//         .await
 591//         .unwrap();
 592
 593//     let workspace_a = client_a
 594//         .build_workspace(&project_a, cx_a)
 595//         .root(cx_a)
 596//         .unwrap();
 597//     let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());
 598
 599//     let workspace_b = client_b
 600//         .build_workspace(&project_b, cx_b)
 601//         .root(cx_b)
 602//         .unwrap();
 603//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
 604
 605//     let client_b_id = project_a.update(cx_a, |project, _| {
 606//         project.collaborators().values().next().unwrap().peer_id
 607//     });
 608
 609//     //Open 1, 3 in that order on client A
 610//     workspace_a
 611//         .update(cx_a, |workspace, cx| {
 612//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
 613//         })
 614//         .await
 615//         .unwrap();
 616//     workspace_a
 617//         .update(cx_a, |workspace, cx| {
 618//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
 619//         })
 620//         .await
 621//         .unwrap();
 622
 623//     let pane_paths = |pane: &View<workspace::Pane>, cx: &mut TestAppContext| {
 624//         pane.update(cx, |pane, cx| {
 625//             pane.items()
 626//                 .map(|item| {
 627//                     item.project_path(cx)
 628//                         .unwrap()
 629//                         .path
 630//                         .to_str()
 631//                         .unwrap()
 632//                         .to_owned()
 633//                 })
 634//                 .collect::<Vec<_>>()
 635//         })
 636//     };
 637
 638//     //Verify that the tabs opened in the order we expect
 639//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
 640
 641//     //Follow client B as client A
 642//     workspace_a
 643//         .update(cx_a, |workspace, cx| {
 644//             workspace.follow(client_b_id, cx).unwrap()
 645//         })
 646//         .await
 647//         .unwrap();
 648
 649//     //Open just 2 on client B
 650//     workspace_b
 651//         .update(cx_b, |workspace, cx| {
 652//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
 653//         })
 654//         .await
 655//         .unwrap();
 656//     executor.run_until_parked();
 657
 658//     // Verify that newly opened followed file is at the end
 659//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
 660
 661//     //Open just 1 on client B
 662//     workspace_b
 663//         .update(cx_b, |workspace, cx| {
 664//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
 665//         })
 666//         .await
 667//         .unwrap();
 668//     assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
 669//     executor.run_until_parked();
 670
 671//     // Verify that following into 1 did not reorder
 672//     assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
 673// }
 674
 675// #[gpui::test(iterations = 10)]
 676// async fn test_peers_following_each_other(
 677//     executor: BackgroundExecutor,
 678//     cx_a: &mut TestAppContext,
 679//     cx_b: &mut TestAppContext,
 680// ) {
 681//     let mut server = TestServer::start(executor.clone()).await;
 682//     let client_a = server.create_client(cx_a, "user_a").await;
 683//     let client_b = server.create_client(cx_b, "user_b").await;
 684//     server
 685//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 686//         .await;
 687//     let active_call_a = cx_a.read(ActiveCall::global);
 688//     let active_call_b = cx_b.read(ActiveCall::global);
 689
 690//     cx_a.update(editor::init);
 691//     cx_b.update(editor::init);
 692
 693//     // Client A shares a project.
 694//     client_a
 695//         .fs()
 696//         .insert_tree(
 697//             "/a",
 698//             json!({
 699//                 "1.txt": "one",
 700//                 "2.txt": "two",
 701//                 "3.txt": "three",
 702//                 "4.txt": "four",
 703//             }),
 704//         )
 705//         .await;
 706//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 707//     active_call_a
 708//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
 709//         .await
 710//         .unwrap();
 711//     let project_id = active_call_a
 712//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 713//         .await
 714//         .unwrap();
 715
 716//     // Client B joins the project.
 717//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 718//     active_call_b
 719//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
 720//         .await
 721//         .unwrap();
 722
 723//     // Client A opens a file.
 724//     let workspace_a = client_a
 725//         .build_workspace(&project_a, cx_a)
 726//         .root(cx_a)
 727//         .unwrap();
 728//     workspace_a
 729//         .update(cx_a, |workspace, cx| {
 730//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
 731//         })
 732//         .await
 733//         .unwrap()
 734//         .downcast::<Editor>()
 735//         .unwrap();
 736
 737//     // Client B opens a different file.
 738//     let workspace_b = client_b
 739//         .build_workspace(&project_b, cx_b)
 740//         .root(cx_b)
 741//         .unwrap();
 742//     workspace_b
 743//         .update(cx_b, |workspace, cx| {
 744//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
 745//         })
 746//         .await
 747//         .unwrap()
 748//         .downcast::<Editor>()
 749//         .unwrap();
 750
 751//     // Clients A and B follow each other in split panes
 752//     workspace_a.update(cx_a, |workspace, cx| {
 753//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
 754//     });
 755//     workspace_a
 756//         .update(cx_a, |workspace, cx| {
 757//             workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
 758//         })
 759//         .await
 760//         .unwrap();
 761//     workspace_b.update(cx_b, |workspace, cx| {
 762//         workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
 763//     });
 764//     workspace_b
 765//         .update(cx_b, |workspace, cx| {
 766//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
 767//         })
 768//         .await
 769//         .unwrap();
 770
 771//     // Clients A and B return focus to the original files they had open
 772//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
 773//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
 774//     executor.run_until_parked();
 775
 776//     // Both clients see the other client's focused file in their right pane.
 777//     assert_eq!(
 778//         pane_summaries(&workspace_a, cx_a),
 779//         &[
 780//             PaneSummary {
 781//                 active: true,
 782//                 leader: None,
 783//                 items: vec![(true, "1.txt".into())]
 784//             },
 785//             PaneSummary {
 786//                 active: false,
 787//                 leader: client_b.peer_id(),
 788//                 items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
 789//             },
 790//         ]
 791//     );
 792//     assert_eq!(
 793//         pane_summaries(&workspace_b, cx_b),
 794//         &[
 795//             PaneSummary {
 796//                 active: true,
 797//                 leader: None,
 798//                 items: vec![(true, "2.txt".into())]
 799//             },
 800//             PaneSummary {
 801//                 active: false,
 802//                 leader: client_a.peer_id(),
 803//                 items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
 804//             },
 805//         ]
 806//     );
 807
 808//     // Clients A and B each open a new file.
 809//     workspace_a
 810//         .update(cx_a, |workspace, cx| {
 811//             workspace.open_path((worktree_id, "3.txt"), None, true, cx)
 812//         })
 813//         .await
 814//         .unwrap();
 815
 816//     workspace_b
 817//         .update(cx_b, |workspace, cx| {
 818//             workspace.open_path((worktree_id, "4.txt"), None, true, cx)
 819//         })
 820//         .await
 821//         .unwrap();
 822//     executor.run_until_parked();
 823
 824//     // Both client's see the other client open the new file, but keep their
 825//     // focus on their own active pane.
 826//     assert_eq!(
 827//         pane_summaries(&workspace_a, cx_a),
 828//         &[
 829//             PaneSummary {
 830//                 active: true,
 831//                 leader: None,
 832//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
 833//             },
 834//             PaneSummary {
 835//                 active: false,
 836//                 leader: client_b.peer_id(),
 837//                 items: vec![
 838//                     (false, "1.txt".into()),
 839//                     (false, "2.txt".into()),
 840//                     (true, "4.txt".into())
 841//                 ]
 842//             },
 843//         ]
 844//     );
 845//     assert_eq!(
 846//         pane_summaries(&workspace_b, cx_b),
 847//         &[
 848//             PaneSummary {
 849//                 active: true,
 850//                 leader: None,
 851//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
 852//             },
 853//             PaneSummary {
 854//                 active: false,
 855//                 leader: client_a.peer_id(),
 856//                 items: vec![
 857//                     (false, "2.txt".into()),
 858//                     (false, "1.txt".into()),
 859//                     (true, "3.txt".into())
 860//                 ]
 861//             },
 862//         ]
 863//     );
 864
 865//     // Client A focuses their right pane, in which they're following client B.
 866//     workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
 867//     executor.run_until_parked();
 868
 869//     // Client B sees that client A is now looking at the same file as them.
 870//     assert_eq!(
 871//         pane_summaries(&workspace_a, cx_a),
 872//         &[
 873//             PaneSummary {
 874//                 active: false,
 875//                 leader: None,
 876//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
 877//             },
 878//             PaneSummary {
 879//                 active: true,
 880//                 leader: client_b.peer_id(),
 881//                 items: vec![
 882//                     (false, "1.txt".into()),
 883//                     (false, "2.txt".into()),
 884//                     (true, "4.txt".into())
 885//                 ]
 886//             },
 887//         ]
 888//     );
 889//     assert_eq!(
 890//         pane_summaries(&workspace_b, cx_b),
 891//         &[
 892//             PaneSummary {
 893//                 active: true,
 894//                 leader: None,
 895//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
 896//             },
 897//             PaneSummary {
 898//                 active: false,
 899//                 leader: client_a.peer_id(),
 900//                 items: vec![
 901//                     (false, "2.txt".into()),
 902//                     (false, "1.txt".into()),
 903//                     (false, "3.txt".into()),
 904//                     (true, "4.txt".into())
 905//                 ]
 906//             },
 907//         ]
 908//     );
 909
 910//     // Client B focuses their right pane, in which they're following client A,
 911//     // who is following them.
 912//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
 913//     executor.run_until_parked();
 914
 915//     // Client A sees that client B is now looking at the same file as them.
 916//     assert_eq!(
 917//         pane_summaries(&workspace_b, cx_b),
 918//         &[
 919//             PaneSummary {
 920//                 active: false,
 921//                 leader: None,
 922//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
 923//             },
 924//             PaneSummary {
 925//                 active: true,
 926//                 leader: client_a.peer_id(),
 927//                 items: vec![
 928//                     (false, "2.txt".into()),
 929//                     (false, "1.txt".into()),
 930//                     (false, "3.txt".into()),
 931//                     (true, "4.txt".into())
 932//                 ]
 933//             },
 934//         ]
 935//     );
 936//     assert_eq!(
 937//         pane_summaries(&workspace_a, cx_a),
 938//         &[
 939//             PaneSummary {
 940//                 active: false,
 941//                 leader: None,
 942//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
 943//             },
 944//             PaneSummary {
 945//                 active: true,
 946//                 leader: client_b.peer_id(),
 947//                 items: vec![
 948//                     (false, "1.txt".into()),
 949//                     (false, "2.txt".into()),
 950//                     (true, "4.txt".into())
 951//                 ]
 952//             },
 953//         ]
 954//     );
 955
 956//     // Client B focuses a file that they previously followed A to, breaking
 957//     // the follow.
 958//     workspace_b.update(cx_b, |workspace, cx| {
 959//         workspace.active_pane().update(cx, |pane, cx| {
 960//             pane.activate_prev_item(true, cx);
 961//         });
 962//     });
 963//     executor.run_until_parked();
 964
 965//     // Both clients see that client B is looking at that previous file.
 966//     assert_eq!(
 967//         pane_summaries(&workspace_b, cx_b),
 968//         &[
 969//             PaneSummary {
 970//                 active: false,
 971//                 leader: None,
 972//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
 973//             },
 974//             PaneSummary {
 975//                 active: true,
 976//                 leader: None,
 977//                 items: vec![
 978//                     (false, "2.txt".into()),
 979//                     (false, "1.txt".into()),
 980//                     (true, "3.txt".into()),
 981//                     (false, "4.txt".into())
 982//                 ]
 983//             },
 984//         ]
 985//     );
 986//     assert_eq!(
 987//         pane_summaries(&workspace_a, cx_a),
 988//         &[
 989//             PaneSummary {
 990//                 active: false,
 991//                 leader: None,
 992//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
 993//             },
 994//             PaneSummary {
 995//                 active: true,
 996//                 leader: client_b.peer_id(),
 997//                 items: vec![
 998//                     (false, "1.txt".into()),
 999//                     (false, "2.txt".into()),
1000//                     (false, "4.txt".into()),
1001//                     (true, "3.txt".into()),
1002//                 ]
1003//             },
1004//         ]
1005//     );
1006
1007//     // Client B closes tabs, some of which were originally opened by client A,
1008//     // and some of which were originally opened by client B.
1009//     workspace_b.update(cx_b, |workspace, cx| {
1010//         workspace.active_pane().update(cx, |pane, cx| {
1011//             pane.close_inactive_items(&Default::default(), cx)
1012//                 .unwrap()
1013//                 .detach();
1014//         });
1015//     });
1016
1017//     executor.run_until_parked();
1018
1019//     // Both clients see that Client B is looking at the previous tab.
1020//     assert_eq!(
1021//         pane_summaries(&workspace_b, cx_b),
1022//         &[
1023//             PaneSummary {
1024//                 active: false,
1025//                 leader: None,
1026//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1027//             },
1028//             PaneSummary {
1029//                 active: true,
1030//                 leader: None,
1031//                 items: vec![(true, "3.txt".into()),]
1032//             },
1033//         ]
1034//     );
1035//     assert_eq!(
1036//         pane_summaries(&workspace_a, cx_a),
1037//         &[
1038//             PaneSummary {
1039//                 active: false,
1040//                 leader: None,
1041//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1042//             },
1043//             PaneSummary {
1044//                 active: true,
1045//                 leader: client_b.peer_id(),
1046//                 items: vec![
1047//                     (false, "1.txt".into()),
1048//                     (false, "2.txt".into()),
1049//                     (false, "4.txt".into()),
1050//                     (true, "3.txt".into()),
1051//                 ]
1052//             },
1053//         ]
1054//     );
1055
1056//     // Client B follows client A again.
1057//     workspace_b
1058//         .update(cx_b, |workspace, cx| {
1059//             workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
1060//         })
1061//         .await
1062//         .unwrap();
1063
1064//     // Client A cycles through some tabs.
1065//     workspace_a.update(cx_a, |workspace, cx| {
1066//         workspace.active_pane().update(cx, |pane, cx| {
1067//             pane.activate_prev_item(true, cx);
1068//         });
1069//     });
1070//     executor.run_until_parked();
1071
1072//     // Client B follows client A into those tabs.
1073//     assert_eq!(
1074//         pane_summaries(&workspace_a, cx_a),
1075//         &[
1076//             PaneSummary {
1077//                 active: false,
1078//                 leader: None,
1079//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1080//             },
1081//             PaneSummary {
1082//                 active: true,
1083//                 leader: None,
1084//                 items: vec![
1085//                     (false, "1.txt".into()),
1086//                     (false, "2.txt".into()),
1087//                     (true, "4.txt".into()),
1088//                     (false, "3.txt".into()),
1089//                 ]
1090//             },
1091//         ]
1092//     );
1093//     assert_eq!(
1094//         pane_summaries(&workspace_b, cx_b),
1095//         &[
1096//             PaneSummary {
1097//                 active: false,
1098//                 leader: None,
1099//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1100//             },
1101//             PaneSummary {
1102//                 active: true,
1103//                 leader: client_a.peer_id(),
1104//                 items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
1105//             },
1106//         ]
1107//     );
1108
1109//     workspace_a.update(cx_a, |workspace, cx| {
1110//         workspace.active_pane().update(cx, |pane, cx| {
1111//             pane.activate_prev_item(true, cx);
1112//         });
1113//     });
1114//     executor.run_until_parked();
1115
1116//     assert_eq!(
1117//         pane_summaries(&workspace_a, cx_a),
1118//         &[
1119//             PaneSummary {
1120//                 active: false,
1121//                 leader: None,
1122//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1123//             },
1124//             PaneSummary {
1125//                 active: true,
1126//                 leader: None,
1127//                 items: vec![
1128//                     (false, "1.txt".into()),
1129//                     (true, "2.txt".into()),
1130//                     (false, "4.txt".into()),
1131//                     (false, "3.txt".into()),
1132//                 ]
1133//             },
1134//         ]
1135//     );
1136//     assert_eq!(
1137//         pane_summaries(&workspace_b, cx_b),
1138//         &[
1139//             PaneSummary {
1140//                 active: false,
1141//                 leader: None,
1142//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1143//             },
1144//             PaneSummary {
1145//                 active: true,
1146//                 leader: client_a.peer_id(),
1147//                 items: vec![
1148//                     (false, "3.txt".into()),
1149//                     (false, "4.txt".into()),
1150//                     (true, "2.txt".into())
1151//                 ]
1152//             },
1153//         ]
1154//     );
1155
1156//     workspace_a.update(cx_a, |workspace, cx| {
1157//         workspace.active_pane().update(cx, |pane, cx| {
1158//             pane.activate_prev_item(true, cx);
1159//         });
1160//     });
1161//     executor.run_until_parked();
1162
1163//     assert_eq!(
1164//         pane_summaries(&workspace_a, cx_a),
1165//         &[
1166//             PaneSummary {
1167//                 active: false,
1168//                 leader: None,
1169//                 items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1170//             },
1171//             PaneSummary {
1172//                 active: true,
1173//                 leader: None,
1174//                 items: vec![
1175//                     (true, "1.txt".into()),
1176//                     (false, "2.txt".into()),
1177//                     (false, "4.txt".into()),
1178//                     (false, "3.txt".into()),
1179//                 ]
1180//             },
1181//         ]
1182//     );
1183//     assert_eq!(
1184//         pane_summaries(&workspace_b, cx_b),
1185//         &[
1186//             PaneSummary {
1187//                 active: false,
1188//                 leader: None,
1189//                 items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1190//             },
1191//             PaneSummary {
1192//                 active: true,
1193//                 leader: client_a.peer_id(),
1194//                 items: vec![
1195//                     (false, "3.txt".into()),
1196//                     (false, "4.txt".into()),
1197//                     (false, "2.txt".into()),
1198//                     (true, "1.txt".into()),
1199//                 ]
1200//             },
1201//         ]
1202//     );
1203// }
1204
1205// #[gpui::test(iterations = 10)]
1206// async fn test_auto_unfollowing(
1207//     executor: BackgroundExecutor,
1208//     cx_a: &mut TestAppContext,
1209//     cx_b: &mut TestAppContext,
1210// ) {
1211//     // 2 clients connect to a server.
1212//     let mut server = TestServer::start(executor.clone()).await;
1213//     let client_a = server.create_client(cx_a, "user_a").await;
1214//     let client_b = server.create_client(cx_b, "user_b").await;
1215//     server
1216//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1217//         .await;
1218//     let active_call_a = cx_a.read(ActiveCall::global);
1219//     let active_call_b = cx_b.read(ActiveCall::global);
1220
1221//     cx_a.update(editor::init);
1222//     cx_b.update(editor::init);
1223
1224//     // Client A shares a project.
1225//     client_a
1226//         .fs()
1227//         .insert_tree(
1228//             "/a",
1229//             json!({
1230//                 "1.txt": "one",
1231//                 "2.txt": "two",
1232//                 "3.txt": "three",
1233//             }),
1234//         )
1235//         .await;
1236//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1237//     active_call_a
1238//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1239//         .await
1240//         .unwrap();
1241
1242//     let project_id = active_call_a
1243//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1244//         .await
1245//         .unwrap();
1246//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
1247//     active_call_b
1248//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1249//         .await
1250//         .unwrap();
1251
1252//     todo!("could be wrong")
1253//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1254//     let cx_a = &mut cx_a;
1255//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1256//     let cx_b = &mut cx_b;
1257
1258//     // Client A opens some editors.
1259//     let workspace_a = client_a
1260//         .build_workspace(&project_a, cx_a)
1261//         .root(cx_a)
1262//         .unwrap();
1263//     let _editor_a1 = workspace_a
1264//         .update(cx_a, |workspace, cx| {
1265//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
1266//         })
1267//         .await
1268//         .unwrap()
1269//         .downcast::<Editor>()
1270//         .unwrap();
1271
1272//     // Client B starts following client A.
1273//     let workspace_b = client_b
1274//         .build_workspace(&project_b, cx_b)
1275//         .root(cx_b)
1276//         .unwrap();
1277//     let pane_b = workspace_b.update(cx_b, |workspace, _| workspace.active_pane().clone());
1278//     let leader_id = project_b.update(cx_b, |project, _| {
1279//         project.collaborators().values().next().unwrap().peer_id
1280//     });
1281//     workspace_b
1282//         .update(cx_b, |workspace, cx| {
1283//             workspace.follow(leader_id, cx).unwrap()
1284//         })
1285//         .await
1286//         .unwrap();
1287//     assert_eq!(
1288//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1289//         Some(leader_id)
1290//     );
1291//     let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {
1292//         workspace
1293//             .active_item(cx)
1294//             .unwrap()
1295//             .downcast::<Editor>()
1296//             .unwrap()
1297//     });
1298
1299//     // When client B moves, it automatically stops following client A.
1300//     editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
1301//     assert_eq!(
1302//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1303//         None
1304//     );
1305
1306//     workspace_b
1307//         .update(cx_b, |workspace, cx| {
1308//             workspace.follow(leader_id, cx).unwrap()
1309//         })
1310//         .await
1311//         .unwrap();
1312//     assert_eq!(
1313//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1314//         Some(leader_id)
1315//     );
1316
1317//     // When client B edits, it automatically stops following client A.
1318//     editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
1319//     assert_eq!(
1320//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1321//         None
1322//     );
1323
1324//     workspace_b
1325//         .update(cx_b, |workspace, cx| {
1326//             workspace.follow(leader_id, cx).unwrap()
1327//         })
1328//         .await
1329//         .unwrap();
1330//     assert_eq!(
1331//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1332//         Some(leader_id)
1333//     );
1334
1335//     // When client B scrolls, it automatically stops following client A.
1336//     editor_b2.update(cx_b, |editor, cx| {
1337//         editor.set_scroll_position(point(0., 3.), cx)
1338//     });
1339//     assert_eq!(
1340//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1341//         None
1342//     );
1343
1344//     workspace_b
1345//         .update(cx_b, |workspace, cx| {
1346//             workspace.follow(leader_id, cx).unwrap()
1347//         })
1348//         .await
1349//         .unwrap();
1350//     assert_eq!(
1351//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1352//         Some(leader_id)
1353//     );
1354
1355//     // When client B activates a different pane, it continues following client A in the original pane.
1356//     workspace_b.update(cx_b, |workspace, cx| {
1357//         workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
1358//     });
1359//     assert_eq!(
1360//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1361//         Some(leader_id)
1362//     );
1363
1364//     workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
1365//     assert_eq!(
1366//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1367//         Some(leader_id)
1368//     );
1369
1370//     // When client B activates a different item in the original pane, it automatically stops following client A.
1371//     workspace_b
1372//         .update(cx_b, |workspace, cx| {
1373//             workspace.open_path((worktree_id, "2.txt"), None, true, cx)
1374//         })
1375//         .await
1376//         .unwrap();
1377//     assert_eq!(
1378//         workspace_b.update(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1379//         None
1380//     );
1381// }
1382
1383// #[gpui::test(iterations = 10)]
1384// async fn test_peers_simultaneously_following_each_other(
1385//     executor: BackgroundExecutor,
1386//     cx_a: &mut TestAppContext,
1387//     cx_b: &mut TestAppContext,
1388// ) {
1389//     let mut server = TestServer::start(executor.clone()).await;
1390//     let client_a = server.create_client(cx_a, "user_a").await;
1391//     let client_b = server.create_client(cx_b, "user_b").await;
1392//     server
1393//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1394//         .await;
1395//     let active_call_a = cx_a.read(ActiveCall::global);
1396
1397//     cx_a.update(editor::init);
1398//     cx_b.update(editor::init);
1399
1400//     client_a.fs().insert_tree("/a", json!({})).await;
1401//     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1402//     let workspace_a = client_a
1403//         .build_workspace(&project_a, cx_a)
1404//         .root(cx_a)
1405//         .unwrap();
1406//     let project_id = active_call_a
1407//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1408//         .await
1409//         .unwrap();
1410
1411//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
1412//     let workspace_b = client_b
1413//         .build_workspace(&project_b, cx_b)
1414//         .root(cx_b)
1415//         .unwrap();
1416
1417//     executor.run_until_parked();
1418//     let client_a_id = project_b.update(cx_b, |project, _| {
1419//         project.collaborators().values().next().unwrap().peer_id
1420//     });
1421//     let client_b_id = project_a.update(cx_a, |project, _| {
1422//         project.collaborators().values().next().unwrap().peer_id
1423//     });
1424
1425//     let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
1426//         workspace.follow(client_b_id, cx).unwrap()
1427//     });
1428//     let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
1429//         workspace.follow(client_a_id, cx).unwrap()
1430//     });
1431
1432//     futures::try_join!(a_follow_b, b_follow_a).unwrap();
1433//     workspace_a.update(cx_a, |workspace, _| {
1434//         assert_eq!(
1435//             workspace.leader_for_pane(workspace.active_pane()),
1436//             Some(client_b_id)
1437//         );
1438//     });
1439//     workspace_b.update(cx_b, |workspace, _| {
1440//         assert_eq!(
1441//             workspace.leader_for_pane(workspace.active_pane()),
1442//             Some(client_a_id)
1443//         );
1444//     });
1445// }
1446
1447// #[gpui::test(iterations = 10)]
1448// async fn test_following_across_workspaces(
1449//     executor: BackgroundExecutor,
1450//     cx_a: &mut TestAppContext,
1451//     cx_b: &mut TestAppContext,
1452// ) {
1453//     // a and b join a channel/call
1454//     // a shares project 1
1455//     // b shares project 2
1456//     //
1457//     // b follows a: causes project 2 to be joined, and b to follow a.
1458//     // b opens a different file in project 2, a follows b
1459//     // b opens a different file in project 1, a cannot follow b
1460//     // b shares the project, a joins the project and follows b
1461//     let mut server = TestServer::start(executor.clone()).await;
1462//     let client_a = server.create_client(cx_a, "user_a").await;
1463//     let client_b = server.create_client(cx_b, "user_b").await;
1464//     cx_a.update(editor::init);
1465//     cx_b.update(editor::init);
1466
1467//     client_a
1468//         .fs()
1469//         .insert_tree(
1470//             "/a",
1471//             json!({
1472//                 "w.rs": "",
1473//                 "x.rs": "",
1474//             }),
1475//         )
1476//         .await;
1477
1478//     client_b
1479//         .fs()
1480//         .insert_tree(
1481//             "/b",
1482//             json!({
1483//                 "y.rs": "",
1484//                 "z.rs": "",
1485//             }),
1486//         )
1487//         .await;
1488
1489//     server
1490//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1491//         .await;
1492//     let active_call_a = cx_a.read(ActiveCall::global);
1493//     let active_call_b = cx_b.read(ActiveCall::global);
1494
1495//     let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
1496//     let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
1497
1498//     let workspace_a = client_a
1499//         .build_workspace(&project_a, cx_a)
1500//         .root(cx_a)
1501//         .unwrap();
1502//     let workspace_b = client_b
1503//         .build_workspace(&project_b, cx_b)
1504//         .root(cx_b)
1505//         .unwrap();
1506
1507//     cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
1508//     cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
1509
1510//     active_call_a
1511//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1512//         .await
1513//         .unwrap();
1514
1515//     active_call_a
1516//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1517//         .await
1518//         .unwrap();
1519//     active_call_b
1520//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1521//         .await
1522//         .unwrap();
1523
1524//     todo!("could be wrong")
1525//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1526//     let cx_a = &mut cx_a;
1527//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1528//     let cx_b = &mut cx_b;
1529
1530//     workspace_a
1531//         .update(cx_a, |workspace, cx| {
1532//             workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
1533//         })
1534//         .await
1535//         .unwrap();
1536
1537//     executor.run_until_parked();
1538//     assert_eq!(visible_push_notifications(cx_b).len(), 1);
1539
1540//     workspace_b.update(cx_b, |workspace, cx| {
1541//         workspace
1542//             .follow(client_a.peer_id().unwrap(), cx)
1543//             .unwrap()
1544//             .detach()
1545//     });
1546
1547//     executor.run_until_parked();
1548//     let workspace_b_project_a = cx_b
1549//         .windows()
1550//         .iter()
1551//         .max_by_key(|window| window.item_id())
1552//         .unwrap()
1553//         .downcast::<Workspace>()
1554//         .unwrap()
1555//         .root(cx_b)
1556//         .unwrap();
1557
1558//     // assert that b is following a in project a in w.rs
1559//     workspace_b_project_a.update(cx_b, |workspace, cx| {
1560//         assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
1561//         assert_eq!(
1562//             client_a.peer_id(),
1563//             workspace.leader_for_pane(workspace.active_pane())
1564//         );
1565//         let item = workspace.active_item(cx).unwrap();
1566//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
1567//     });
1568
1569//     // TODO: in app code, this would be done by the collab_ui.
1570//     active_call_b
1571//         .update(cx_b, |call, cx| {
1572//             let project = workspace_b_project_a.read(cx).project().clone();
1573//             call.set_location(Some(&project), cx)
1574//         })
1575//         .await
1576//         .unwrap();
1577
1578//     // assert that there are no share notifications open
1579//     assert_eq!(visible_push_notifications(cx_b).len(), 0);
1580
1581//     // b moves to x.rs in a's project, and a follows
1582//     workspace_b_project_a
1583//         .update(cx_b, |workspace, cx| {
1584//             workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
1585//         })
1586//         .await
1587//         .unwrap();
1588
1589//     executor.run_until_parked();
1590//     workspace_b_project_a.update(cx_b, |workspace, cx| {
1591//         let item = workspace.active_item(cx).unwrap();
1592//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
1593//     });
1594
1595//     workspace_a.update(cx_a, |workspace, cx| {
1596//         workspace
1597//             .follow(client_b.peer_id().unwrap(), cx)
1598//             .unwrap()
1599//             .detach()
1600//     });
1601
1602//     executor.run_until_parked();
1603//     workspace_a.update(cx_a, |workspace, cx| {
1604//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1605//         assert_eq!(
1606//             client_b.peer_id(),
1607//             workspace.leader_for_pane(workspace.active_pane())
1608//         );
1609//         let item = workspace.active_pane().read(cx).active_item().unwrap();
1610//         assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs".into());
1611//     });
1612
1613//     // b moves to y.rs in b's project, a is still following but can't yet see
1614//     workspace_b
1615//         .update(cx_b, |workspace, cx| {
1616//             workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
1617//         })
1618//         .await
1619//         .unwrap();
1620
1621//     // TODO: in app code, this would be done by the collab_ui.
1622//     active_call_b
1623//         .update(cx_b, |call, cx| {
1624//             let project = workspace_b.read(cx).project().clone();
1625//             call.set_location(Some(&project), cx)
1626//         })
1627//         .await
1628//         .unwrap();
1629
1630//     let project_b_id = active_call_b
1631//         .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1632//         .await
1633//         .unwrap();
1634
1635//     executor.run_until_parked();
1636//     assert_eq!(visible_push_notifications(cx_a).len(), 1);
1637//     cx_a.update(|cx| {
1638//         workspace::join_remote_project(
1639//             project_b_id,
1640//             client_b.user_id().unwrap(),
1641//             client_a.app_state.clone(),
1642//             cx,
1643//         )
1644//     })
1645//     .await
1646//     .unwrap();
1647
1648//     executor.run_until_parked();
1649
1650//     assert_eq!(visible_push_notifications(cx_a).len(), 0);
1651//     let workspace_a_project_b = cx_a
1652//         .windows()
1653//         .iter()
1654//         .max_by_key(|window| window.item_id())
1655//         .unwrap()
1656//         .downcast::<Workspace>()
1657//         .unwrap()
1658//         .root(cx_a)
1659//         .unwrap();
1660
1661//     workspace_a_project_b.update(cx_a, |workspace, cx| {
1662//         assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
1663//         assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1664//         assert_eq!(
1665//             client_b.peer_id(),
1666//             workspace.leader_for_pane(workspace.active_pane())
1667//         );
1668//         let item = workspace.active_item(cx).unwrap();
1669//         assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
1670//     });
1671// }
1672
1673// #[gpui::test]
1674// async fn test_following_into_excluded_file(
1675//     executor: BackgroundExecutor,
1676//     mut cx_a: &mut TestAppContext,
1677//     mut cx_b: &mut TestAppContext,
1678// ) {
1679//     let mut server = TestServer::start(executor.clone()).await;
1680//     let client_a = server.create_client(cx_a, "user_a").await;
1681//     let client_b = server.create_client(cx_b, "user_b").await;
1682//     for cx in [&mut cx_a, &mut cx_b] {
1683//         cx.update(|cx| {
1684//             cx.update_global::<SettingsStore, _>(|store, cx| {
1685//                 store.update_user_settings::<ProjectSettings>(cx, |project_settings| {
1686//                     project_settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
1687//                 });
1688//             });
1689//         });
1690//     }
1691//     server
1692//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1693//         .await;
1694//     let active_call_a = cx_a.read(ActiveCall::global);
1695//     let active_call_b = cx_b.read(ActiveCall::global);
1696
1697//     cx_a.update(editor::init);
1698//     cx_b.update(editor::init);
1699
1700//     client_a
1701//         .fs()
1702//         .insert_tree(
1703//             "/a",
1704//             json!({
1705//                 ".git": {
1706//                     "COMMIT_EDITMSG": "write your commit message here",
1707//                 },
1708//                 "1.txt": "one\none\none",
1709//                 "2.txt": "two\ntwo\ntwo",
1710//                 "3.txt": "three\nthree\nthree",
1711//             }),
1712//         )
1713//         .await;
1714//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1715//     active_call_a
1716//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1717//         .await
1718//         .unwrap();
1719
1720//     let project_id = active_call_a
1721//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1722//         .await
1723//         .unwrap();
1724//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
1725//     active_call_b
1726//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1727//         .await
1728//         .unwrap();
1729
1730//     let window_a = client_a.build_workspace(&project_a, cx_a);
1731//     let workspace_a = window_a.root(cx_a).unwrap();
1732//     let peer_id_a = client_a.peer_id().unwrap();
1733//     let window_b = client_b.build_workspace(&project_b, cx_b);
1734//     let workspace_b = window_b.root(cx_b).unwrap();
1735
1736//     todo!("could be wrong")
1737//     let mut cx_a = VisualTestContext::from_window(*window_a, cx_a);
1738//     let cx_a = &mut cx_a;
1739//     let mut cx_b = VisualTestContext::from_window(*window_b, cx_b);
1740//     let cx_b = &mut cx_b;
1741
1742//     // Client A opens editors for a regular file and an excluded file.
1743//     let editor_for_regular = workspace_a
1744//         .update(cx_a, |workspace, cx| {
1745//             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
1746//         })
1747//         .await
1748//         .unwrap()
1749//         .downcast::<Editor>()
1750//         .unwrap();
1751//     let editor_for_excluded_a = workspace_a
1752//         .update(cx_a, |workspace, cx| {
1753//             workspace.open_path((worktree_id, ".git/COMMIT_EDITMSG"), None, true, cx)
1754//         })
1755//         .await
1756//         .unwrap()
1757//         .downcast::<Editor>()
1758//         .unwrap();
1759
1760//     // Client A updates their selections in those editors
1761//     editor_for_regular.update(cx_a, |editor, cx| {
1762//         editor.handle_input("a", cx);
1763//         editor.handle_input("b", cx);
1764//         editor.handle_input("c", cx);
1765//         editor.select_left(&Default::default(), cx);
1766//         assert_eq!(editor.selections.ranges(cx), vec![3..2]);
1767//     });
1768//     editor_for_excluded_a.update(cx_a, |editor, cx| {
1769//         editor.select_all(&Default::default(), cx);
1770//         editor.handle_input("new commit message", cx);
1771//         editor.select_left(&Default::default(), cx);
1772//         assert_eq!(editor.selections.ranges(cx), vec![18..17]);
1773//     });
1774
1775//     // When client B starts following client A, currently visible file is replicated
1776//     workspace_b
1777//         .update(cx_b, |workspace, cx| {
1778//             workspace.follow(peer_id_a, cx).unwrap()
1779//         })
1780//         .await
1781//         .unwrap();
1782
1783//     let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| {
1784//         workspace
1785//             .active_item(cx)
1786//             .unwrap()
1787//             .downcast::<Editor>()
1788//             .unwrap()
1789//     });
1790//     assert_eq!(
1791//         cx_b.read(|cx| editor_for_excluded_b.project_path(cx)),
1792//         Some((worktree_id, ".git/COMMIT_EDITMSG").into())
1793//     );
1794//     assert_eq!(
1795//         editor_for_excluded_b.update(cx_b, |editor, cx| editor.selections.ranges(cx)),
1796//         vec![18..17]
1797//     );
1798
1799//     // Changes from B to the excluded file are replicated in A's editor
1800//     editor_for_excluded_b.update(cx_b, |editor, cx| {
1801//         editor.handle_input("\nCo-Authored-By: B <b@b.b>", cx);
1802//     });
1803//     executor.run_until_parked();
1804//     editor_for_excluded_a.update(cx_a, |editor, cx| {
1805//         assert_eq!(
1806//             editor.text(cx),
1807//             "new commit messag\nCo-Authored-By: B <b@b.b>"
1808//         );
1809//     });
1810// }
1811
1812// fn visible_push_notifications(
1813//     cx: &mut TestAppContext,
1814// ) -> Vec<gpui::View<ProjectSharedNotification>> {
1815//     let mut ret = Vec::new();
1816//     for window in cx.windows() {
1817//         window.update(cx, |window| {
1818//             if let Some(handle) = window
1819//                 .root_view()
1820//                 .clone()
1821//                 .downcast::<ProjectSharedNotification>()
1822//             {
1823//                 ret.push(handle)
1824//             }
1825//         });
1826//     }
1827//     ret
1828// }
1829
1830// #[derive(Debug, PartialEq, Eq)]
1831// struct PaneSummary {
1832//     active: bool,
1833//     leader: Option<PeerId>,
1834//     items: Vec<(bool, String)>,
1835// }
1836
1837// fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
1838//     cx.read(|cx| {
1839//         let active_call = ActiveCall::global(cx).read(cx);
1840//         let peer_id = active_call.client().peer_id();
1841//         let room = active_call.room().unwrap().read(cx);
1842//         let mut result = room
1843//             .remote_participants()
1844//             .values()
1845//             .map(|participant| participant.peer_id)
1846//             .chain(peer_id)
1847//             .filter_map(|peer_id| {
1848//                 let followers = room.followers_for(peer_id, project_id);
1849//                 if followers.is_empty() {
1850//                     None
1851//                 } else {
1852//                     Some((peer_id, followers.to_vec()))
1853//                 }
1854//             })
1855//             .collect::<Vec<_>>();
1856//         result.sort_by_key(|e| e.0);
1857//         result
1858//     })
1859// }
1860
1861// fn pane_summaries(workspace: &View<Workspace>, cx: &mut WindowContext<'_>) -> Vec<PaneSummary> {
1862//     workspace.update(cx, |workspace, cx| {
1863//         let active_pane = workspace.active_pane();
1864//         workspace
1865//             .panes()
1866//             .iter()
1867//             .map(|pane| {
1868//                 let leader = workspace.leader_for_pane(pane);
1869//                 let active = pane == active_pane;
1870//                 let pane = pane.read(cx);
1871//                 let active_ix = pane.active_item_index();
1872//                 PaneSummary {
1873//                     active,
1874//                     leader,
1875//                     items: pane
1876//                         .items()
1877//                         .enumerate()
1878//                         .map(|(ix, item)| {
1879//                             (
1880//                                 ix == active_ix,
1881//                                 item.tab_description(0, cx)
1882//                                     .map_or(String::new(), |s| s.to_string()),
1883//                             )
1884//                         })
1885//                         .collect(),
1886//                 }
1887//             })
1888//             .collect()
1889//     })
1890// }