integration_tests.rs

   1use crate::{
   2    db::{tests::TestDb, ProjectId, UserId},
   3    rpc::{Executor, Server, Store},
   4    AppState,
   5};
   6use ::rpc::Peer;
   7use anyhow::anyhow;
   8use call::Room;
   9use client::{
  10    self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
  11    Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
  12};
  13use collections::{BTreeMap, HashMap, HashSet};
  14use editor::{
  15    self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset,
  16    ToggleCodeActions, Undo,
  17};
  18use futures::{channel::mpsc, Future, StreamExt as _};
  19use gpui::{
  20    executor::{self, Deterministic},
  21    geometry::vector::vec2f,
  22    test::EmptyView,
  23    ModelHandle, Task, TestAppContext, ViewHandle,
  24};
  25use language::{
  26    range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
  27    LanguageConfig, LanguageRegistry, LineEnding, OffsetRangeExt, Point, Rope,
  28};
  29use lsp::{self, FakeLanguageServer};
  30use parking_lot::Mutex;
  31use project::{
  32    fs::{FakeFs, Fs as _},
  33    search::SearchQuery,
  34    worktree::WorktreeHandle,
  35    DiagnosticSummary, Project, ProjectPath, ProjectStore, WorktreeId,
  36};
  37use rand::prelude::*;
  38use rpc::PeerId;
  39use serde_json::json;
  40use settings::{Formatter, Settings};
  41use sqlx::types::time::OffsetDateTime;
  42use std::{
  43    cell::RefCell,
  44    env,
  45    ops::Deref,
  46    path::{Path, PathBuf},
  47    rc::Rc,
  48    sync::{
  49        atomic::{AtomicBool, Ordering::SeqCst},
  50        Arc,
  51    },
  52    time::Duration,
  53};
  54use theme::ThemeRegistry;
  55use workspace::{Item, SplitDirection, ToggleFollow, Workspace};
  56
  57#[ctor::ctor]
  58fn init_logger() {
  59    if std::env::var("RUST_LOG").is_ok() {
  60        env_logger::init();
  61    }
  62}
  63
  64#[gpui::test(iterations = 10)]
  65async fn test_basic_calls(
  66    deterministic: Arc<Deterministic>,
  67    cx_a: &mut TestAppContext,
  68    cx_b: &mut TestAppContext,
  69    cx_b2: &mut TestAppContext,
  70    cx_c: &mut TestAppContext,
  71) {
  72    deterministic.forbid_parking();
  73    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
  74    let client_a = server.create_client(cx_a, "user_a").await;
  75    let client_b = server.create_client(cx_b, "user_b").await;
  76    let client_c = server.create_client(cx_c, "user_c").await;
  77    server
  78        .make_contacts(vec![
  79            (&client_a, cx_a),
  80            (&client_b, cx_b),
  81            (&client_c, cx_c),
  82        ])
  83        .await;
  84
  85    let room_a = cx_a
  86        .update(|cx| Room::create(client_a.clone(), client_a.user_store.clone(), cx))
  87        .await
  88        .unwrap();
  89    assert_eq!(
  90        room_participants(&room_a, &client_a, cx_a).await,
  91        RoomParticipants {
  92            remote: Default::default(),
  93            pending: Default::default()
  94        }
  95    );
  96
  97    // Call user B from client A.
  98    let mut incoming_call_b = client_b
  99        .user_store
 100        .update(cx_b, |user, _| user.incoming_call());
 101    room_a
 102        .update(cx_a, |room, cx| room.call(client_b.user_id().unwrap(), cx))
 103        .await
 104        .unwrap();
 105
 106    deterministic.run_until_parked();
 107    assert_eq!(
 108        room_participants(&room_a, &client_a, cx_a).await,
 109        RoomParticipants {
 110            remote: Default::default(),
 111            pending: vec!["user_b".to_string()]
 112        }
 113    );
 114
 115    // User B receives the call.
 116    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 117
 118    // User B connects via another client and also receives a ring on the newly-connected client.
 119    let client_b2 = server.create_client(cx_b2, "user_b").await;
 120    let mut incoming_call_b2 = client_b2
 121        .user_store
 122        .update(cx_b2, |user, _| user.incoming_call());
 123    deterministic.run_until_parked();
 124    let _call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
 125
 126    // User B joins the room using the first client.
 127    let room_b = cx_b
 128        .update(|cx| Room::join(&call_b, client_b.clone(), client_b.user_store.clone(), cx))
 129        .await
 130        .unwrap();
 131    assert!(incoming_call_b.next().await.unwrap().is_none());
 132
 133    deterministic.run_until_parked();
 134    assert_eq!(
 135        room_participants(&room_a, &client_a, cx_a).await,
 136        RoomParticipants {
 137            remote: vec!["user_b".to_string()],
 138            pending: Default::default()
 139        }
 140    );
 141    assert_eq!(
 142        room_participants(&room_b, &client_b, cx_b).await,
 143        RoomParticipants {
 144            remote: vec!["user_a".to_string()],
 145            pending: Default::default()
 146        }
 147    );
 148
 149    // Call user C from client B.
 150    let mut incoming_call_c = client_c
 151        .user_store
 152        .update(cx_c, |user, _| user.incoming_call());
 153    room_b
 154        .update(cx_b, |room, cx| room.call(client_c.user_id().unwrap(), cx))
 155        .await
 156        .unwrap();
 157
 158    deterministic.run_until_parked();
 159    assert_eq!(
 160        room_participants(&room_a, &client_a, cx_a).await,
 161        RoomParticipants {
 162            remote: vec!["user_b".to_string()],
 163            pending: vec!["user_c".to_string()]
 164        }
 165    );
 166    assert_eq!(
 167        room_participants(&room_b, &client_b, cx_b).await,
 168        RoomParticipants {
 169            remote: vec!["user_a".to_string()],
 170            pending: vec!["user_c".to_string()]
 171        }
 172    );
 173
 174    // User C receives the call, but declines it.
 175    let _call_c = incoming_call_c.next().await.unwrap().unwrap();
 176    client_c
 177        .user_store
 178        .update(cx_c, |user, _| user.decline_call())
 179        .unwrap();
 180    assert!(incoming_call_c.next().await.unwrap().is_none());
 181
 182    deterministic.run_until_parked();
 183    assert_eq!(
 184        room_participants(&room_a, &client_a, cx_a).await,
 185        RoomParticipants {
 186            remote: vec!["user_b".to_string()],
 187            pending: Default::default()
 188        }
 189    );
 190    assert_eq!(
 191        room_participants(&room_b, &client_b, cx_b).await,
 192        RoomParticipants {
 193            remote: vec!["user_a".to_string()],
 194            pending: Default::default()
 195        }
 196    );
 197
 198    // User A leaves the room.
 199    room_a.update(cx_a, |room, cx| room.leave(cx)).unwrap();
 200    deterministic.run_until_parked();
 201    assert_eq!(
 202        room_participants(&room_a, &client_a, cx_a).await,
 203        RoomParticipants {
 204            remote: Default::default(),
 205            pending: Default::default()
 206        }
 207    );
 208    assert_eq!(
 209        room_participants(&room_b, &client_b, cx_b).await,
 210        RoomParticipants {
 211            remote: Default::default(),
 212            pending: Default::default()
 213        }
 214    );
 215}
 216
 217#[gpui::test(iterations = 10)]
 218async fn test_leaving_room_on_disconnection(
 219    deterministic: Arc<Deterministic>,
 220    cx_a: &mut TestAppContext,
 221    cx_b: &mut TestAppContext,
 222) {
 223    deterministic.forbid_parking();
 224    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 225    let client_a = server.create_client(cx_a, "user_a").await;
 226    let client_b = server.create_client(cx_b, "user_b").await;
 227    server
 228        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 229        .await;
 230
 231    let room_a = cx_a
 232        .update(|cx| Room::create(client_a.clone(), client_a.user_store.clone(), cx))
 233        .await
 234        .unwrap();
 235
 236    // Call user B from client A.
 237    let mut incoming_call_b = client_b
 238        .user_store
 239        .update(cx_b, |user, _| user.incoming_call());
 240    room_a
 241        .update(cx_a, |room, cx| room.call(client_b.user_id().unwrap(), cx))
 242        .await
 243        .unwrap();
 244
 245    // User B receives the call and joins the room.
 246    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 247    let room_b = cx_b
 248        .update(|cx| Room::join(&call_b, client_b.clone(), client_b.user_store.clone(), cx))
 249        .await
 250        .unwrap();
 251    deterministic.run_until_parked();
 252    assert_eq!(
 253        room_participants(&room_a, &client_a, cx_a).await,
 254        RoomParticipants {
 255            remote: vec!["user_b".to_string()],
 256            pending: Default::default()
 257        }
 258    );
 259    assert_eq!(
 260        room_participants(&room_b, &client_b, cx_b).await,
 261        RoomParticipants {
 262            remote: vec!["user_a".to_string()],
 263            pending: Default::default()
 264        }
 265    );
 266
 267    server.disconnect_client(client_a.current_user_id(cx_a));
 268    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 269    assert_eq!(
 270        room_participants(&room_a, &client_a, cx_a).await,
 271        RoomParticipants {
 272            remote: Default::default(),
 273            pending: Default::default()
 274        }
 275    );
 276    assert_eq!(
 277        room_participants(&room_b, &client_b, cx_b).await,
 278        RoomParticipants {
 279            remote: Default::default(),
 280            pending: Default::default()
 281        }
 282    );
 283}
 284
 285#[gpui::test(iterations = 10)]
 286async fn test_share_project(
 287    deterministic: Arc<Deterministic>,
 288    cx_a: &mut TestAppContext,
 289    cx_b: &mut TestAppContext,
 290    cx_b2: &mut TestAppContext,
 291) {
 292    cx_a.foreground().forbid_parking();
 293    let (_, window_b) = cx_b.add_window(|_| EmptyView);
 294    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 295    let client_a = server.create_client(cx_a, "user_a").await;
 296    let client_b = server.create_client(cx_b, "user_b").await;
 297    server
 298        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 299        .await;
 300
 301    client_a
 302        .fs
 303        .insert_tree(
 304            "/a",
 305            json!({
 306                ".gitignore": "ignored-dir",
 307                "a.txt": "a-contents",
 308                "b.txt": "b-contents",
 309                "ignored-dir": {
 310                    "c.txt": "",
 311                    "d.txt": "",
 312                }
 313            }),
 314        )
 315        .await;
 316
 317    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 318    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 319
 320    // Join that project as client B
 321    let client_b_peer_id = client_b.peer_id;
 322    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 323    let replica_id_b = project_b.read_with(cx_b, |project, _| {
 324        assert_eq!(
 325            project
 326                .collaborators()
 327                .get(&client_a.peer_id)
 328                .unwrap()
 329                .user
 330                .github_login,
 331            "user_a"
 332        );
 333        project.replica_id()
 334    });
 335
 336    deterministic.run_until_parked();
 337    project_a.read_with(cx_a, |project, _| {
 338        let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
 339        assert_eq!(client_b_collaborator.replica_id, replica_id_b);
 340        assert_eq!(client_b_collaborator.user.github_login, "user_b");
 341    });
 342    project_b.read_with(cx_b, |project, cx| {
 343        let worktree = project.worktrees(cx).next().unwrap().read(cx);
 344        assert_eq!(
 345            worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
 346            [
 347                Path::new(".gitignore"),
 348                Path::new("a.txt"),
 349                Path::new("b.txt"),
 350                Path::new("ignored-dir"),
 351                Path::new("ignored-dir/c.txt"),
 352                Path::new("ignored-dir/d.txt"),
 353            ]
 354        );
 355    });
 356
 357    // Open the same file as client B and client A.
 358    let buffer_b = project_b
 359        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 360        .await
 361        .unwrap();
 362    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
 363    project_a.read_with(cx_a, |project, cx| {
 364        assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
 365    });
 366    let buffer_a = project_a
 367        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
 368        .await
 369        .unwrap();
 370
 371    let editor_b = cx_b.add_view(&window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 372
 373    // TODO
 374    // // Create a selection set as client B and see that selection set as client A.
 375    // buffer_a
 376    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 1)
 377    //     .await;
 378
 379    // Edit the buffer as client B and see that edit as client A.
 380    editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
 381    buffer_a
 382        .condition(cx_a, |buffer, _| buffer.text() == "ok, b-contents")
 383        .await;
 384
 385    // TODO
 386    // // Remove the selection set as client B, see those selections disappear as client A.
 387    cx_b.update(move |_| drop(editor_b));
 388    // buffer_a
 389    //     .condition(&cx_a, |buffer, _| buffer.selection_sets().count() == 0)
 390    //     .await;
 391
 392    // Client B can join again on a different window because they are already a participant.
 393    let client_b2 = server.create_client(cx_b2, "user_b").await;
 394    let project_b2 = Project::remote(
 395        project_id,
 396        client_b2.client.clone(),
 397        client_b2.user_store.clone(),
 398        client_b2.project_store.clone(),
 399        client_b2.language_registry.clone(),
 400        FakeFs::new(cx_b2.background()),
 401        cx_b2.to_async(),
 402    )
 403    .await
 404    .unwrap();
 405    deterministic.run_until_parked();
 406    project_a.read_with(cx_a, |project, _| {
 407        assert_eq!(project.collaborators().len(), 2);
 408    });
 409    project_b.read_with(cx_b, |project, _| {
 410        assert_eq!(project.collaborators().len(), 2);
 411    });
 412    project_b2.read_with(cx_b2, |project, _| {
 413        assert_eq!(project.collaborators().len(), 2);
 414    });
 415
 416    // Dropping client B's first project removes only that from client A's collaborators.
 417    cx_b.update(move |_| drop(project_b));
 418    deterministic.run_until_parked();
 419    project_a.read_with(cx_a, |project, _| {
 420        assert_eq!(project.collaborators().len(), 1);
 421    });
 422    project_b2.read_with(cx_b2, |project, _| {
 423        assert_eq!(project.collaborators().len(), 1);
 424    });
 425}
 426
 427#[gpui::test(iterations = 10)]
 428async fn test_unshare_project(
 429    deterministic: Arc<Deterministic>,
 430    cx_a: &mut TestAppContext,
 431    cx_b: &mut TestAppContext,
 432) {
 433    cx_a.foreground().forbid_parking();
 434    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 435    let client_a = server.create_client(cx_a, "user_a").await;
 436    let client_b = server.create_client(cx_b, "user_b").await;
 437    server
 438        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 439        .await;
 440
 441    client_a
 442        .fs
 443        .insert_tree(
 444            "/a",
 445            json!({
 446                "a.txt": "a-contents",
 447                "b.txt": "b-contents",
 448            }),
 449        )
 450        .await;
 451
 452    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 453    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 454    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 455    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 456
 457    project_b
 458        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 459        .await
 460        .unwrap();
 461
 462    // When client B leaves the project, it gets automatically unshared.
 463    cx_b.update(|_| drop(project_b));
 464    deterministic.run_until_parked();
 465    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 466
 467    // When client B joins again, the project gets re-shared.
 468    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 469    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 470    project_b2
 471        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 472        .await
 473        .unwrap();
 474
 475    // When client A (the host) leaves, the project gets unshared and guests are notified.
 476    cx_a.update(|_| drop(project_a));
 477    deterministic.run_until_parked();
 478    project_b2.read_with(cx_b, |project, _| {
 479        assert!(project.is_read_only());
 480        assert!(project.collaborators().is_empty());
 481    });
 482}
 483
 484#[gpui::test(iterations = 10)]
 485async fn test_host_disconnect(
 486    deterministic: Arc<Deterministic>,
 487    cx_a: &mut TestAppContext,
 488    cx_b: &mut TestAppContext,
 489    cx_c: &mut TestAppContext,
 490) {
 491    cx_b.update(editor::init);
 492    deterministic.forbid_parking();
 493    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 494    let client_a = server.create_client(cx_a, "user_a").await;
 495    let client_b = server.create_client(cx_b, "user_b").await;
 496    let client_c = server.create_client(cx_c, "user_c").await;
 497    server
 498        .make_contacts(vec![
 499            (&client_a, cx_a),
 500            (&client_b, cx_b),
 501            (&client_c, cx_c),
 502        ])
 503        .await;
 504
 505    client_a
 506        .fs
 507        .insert_tree(
 508            "/a",
 509            json!({
 510                "a.txt": "a-contents",
 511                "b.txt": "b-contents",
 512            }),
 513        )
 514        .await;
 515
 516    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 517    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 518    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 519
 520    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 521    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 522
 523    let (_, workspace_b) =
 524        cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
 525    let editor_b = workspace_b
 526        .update(cx_b, |workspace, cx| {
 527            workspace.open_path((worktree_id, "b.txt"), true, cx)
 528        })
 529        .await
 530        .unwrap()
 531        .downcast::<Editor>()
 532        .unwrap();
 533    cx_b.read(|cx| {
 534        assert_eq!(
 535            cx.focused_view_id(workspace_b.window_id()),
 536            Some(editor_b.id())
 537        );
 538    });
 539    editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
 540    assert!(cx_b.is_window_edited(workspace_b.window_id()));
 541
 542    // Request to join that project as client C
 543    let project_c = cx_c.spawn(|cx| {
 544        Project::remote(
 545            project_id,
 546            client_c.client.clone(),
 547            client_c.user_store.clone(),
 548            client_c.project_store.clone(),
 549            client_c.language_registry.clone(),
 550            FakeFs::new(cx.background()),
 551            cx,
 552        )
 553    });
 554    deterministic.run_until_parked();
 555
 556    // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
 557    server.disconnect_client(client_a.current_user_id(cx_a));
 558    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
 559    project_a
 560        .condition(cx_a, |project, _| project.collaborators().is_empty())
 561        .await;
 562    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 563    project_b
 564        .condition(cx_b, |project, _| project.is_read_only())
 565        .await;
 566    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 567    assert!(matches!(
 568        project_c.await.unwrap_err(),
 569        project::JoinProjectError::HostWentOffline
 570    ));
 571
 572    // Ensure client B's edited state is reset and that the whole window is blurred.
 573    cx_b.read(|cx| {
 574        assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
 575    });
 576    assert!(!cx_b.is_window_edited(workspace_b.window_id()));
 577
 578    // Ensure client B is not prompted to save edits when closing window after disconnecting.
 579    workspace_b
 580        .update(cx_b, |workspace, cx| {
 581            workspace.close(&Default::default(), cx)
 582        })
 583        .unwrap()
 584        .await
 585        .unwrap();
 586    assert_eq!(cx_b.window_ids().len(), 0);
 587    cx_b.update(|_| {
 588        drop(workspace_b);
 589        drop(project_b);
 590    });
 591
 592    // Ensure guests can still join.
 593    let project_b2 = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 594    assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 595    project_b2
 596        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 597        .await
 598        .unwrap();
 599}
 600
 601#[gpui::test(iterations = 10)]
 602async fn test_decline_join_request(
 603    deterministic: Arc<Deterministic>,
 604    cx_a: &mut TestAppContext,
 605    cx_b: &mut TestAppContext,
 606) {
 607    cx_a.foreground().forbid_parking();
 608    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 609    let client_a = server.create_client(cx_a, "user_a").await;
 610    let client_b = server.create_client(cx_b, "user_b").await;
 611    server
 612        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 613        .await;
 614
 615    client_a.fs.insert_tree("/a", json!({})).await;
 616
 617    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 618    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 619
 620    // Request to join that project as client B
 621    let project_b = cx_b.spawn(|cx| {
 622        Project::remote(
 623            project_id,
 624            client_b.client.clone(),
 625            client_b.user_store.clone(),
 626            client_b.project_store.clone(),
 627            client_b.language_registry.clone(),
 628            FakeFs::new(cx.background()),
 629            cx,
 630        )
 631    });
 632    deterministic.run_until_parked();
 633    project_a.update(cx_a, |project, cx| {
 634        project.respond_to_join_request(client_b.user_id().unwrap(), false, cx)
 635    });
 636    assert!(matches!(
 637        project_b.await.unwrap_err(),
 638        project::JoinProjectError::HostDeclined
 639    ));
 640
 641    // Request to join the project again as client B
 642    let project_b = cx_b.spawn(|cx| {
 643        Project::remote(
 644            project_id,
 645            client_b.client.clone(),
 646            client_b.user_store.clone(),
 647            client_b.project_store.clone(),
 648            client_b.language_registry.clone(),
 649            FakeFs::new(cx.background()),
 650            cx,
 651        )
 652    });
 653
 654    // Close the project on the host
 655    deterministic.run_until_parked();
 656    cx_a.update(|_| drop(project_a));
 657    deterministic.run_until_parked();
 658    assert!(matches!(
 659        project_b.await.unwrap_err(),
 660        project::JoinProjectError::HostClosedProject
 661    ));
 662}
 663
 664#[gpui::test(iterations = 10)]
 665async fn test_cancel_join_request(
 666    deterministic: Arc<Deterministic>,
 667    cx_a: &mut TestAppContext,
 668    cx_b: &mut TestAppContext,
 669) {
 670    cx_a.foreground().forbid_parking();
 671    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 672    let client_a = server.create_client(cx_a, "user_a").await;
 673    let client_b = server.create_client(cx_b, "user_b").await;
 674    server
 675        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 676        .await;
 677
 678    client_a.fs.insert_tree("/a", json!({})).await;
 679    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 680    let project_id = project_a.read_with(cx_a, |project, _| project.remote_id().unwrap());
 681
 682    let user_b = client_a
 683        .user_store
 684        .update(cx_a, |store, cx| {
 685            store.get_user(client_b.user_id().unwrap(), cx)
 686        })
 687        .await
 688        .unwrap();
 689
 690    let project_a_events = Rc::new(RefCell::new(Vec::new()));
 691    project_a.update(cx_a, {
 692        let project_a_events = project_a_events.clone();
 693        move |_, cx| {
 694            cx.subscribe(&cx.handle(), move |_, _, event, _| {
 695                project_a_events.borrow_mut().push(event.clone());
 696            })
 697            .detach();
 698        }
 699    });
 700
 701    // Request to join that project as client B
 702    let project_b = cx_b.spawn(|cx| {
 703        Project::remote(
 704            project_id,
 705            client_b.client.clone(),
 706            client_b.user_store.clone(),
 707            client_b.project_store.clone(),
 708            client_b.language_registry.clone(),
 709            FakeFs::new(cx.background()),
 710            cx,
 711        )
 712    });
 713    deterministic.run_until_parked();
 714    assert_eq!(
 715        &*project_a_events.borrow(),
 716        &[project::Event::ContactRequestedJoin(user_b.clone())]
 717    );
 718    project_a_events.borrow_mut().clear();
 719
 720    // Cancel the join request by leaving the project
 721    client_b
 722        .client
 723        .send(proto::LeaveProject { project_id })
 724        .unwrap();
 725    drop(project_b);
 726
 727    deterministic.run_until_parked();
 728    assert_eq!(
 729        &*project_a_events.borrow(),
 730        &[project::Event::ContactCancelledJoinRequest(user_b)]
 731    );
 732}
 733
 734#[gpui::test(iterations = 10)]
 735async fn test_propagate_saves_and_fs_changes(
 736    cx_a: &mut TestAppContext,
 737    cx_b: &mut TestAppContext,
 738    cx_c: &mut TestAppContext,
 739) {
 740    cx_a.foreground().forbid_parking();
 741    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 742    let client_a = server.create_client(cx_a, "user_a").await;
 743    let client_b = server.create_client(cx_b, "user_b").await;
 744    let client_c = server.create_client(cx_c, "user_c").await;
 745    server
 746        .make_contacts(vec![
 747            (&client_a, cx_a),
 748            (&client_b, cx_b),
 749            (&client_c, cx_c),
 750        ])
 751        .await;
 752
 753    client_a
 754        .fs
 755        .insert_tree(
 756            "/a",
 757            json!({
 758                "file1": "",
 759                "file2": ""
 760            }),
 761        )
 762        .await;
 763    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 764    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
 765
 766    // Join that worktree as clients B and C.
 767    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 768    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
 769    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
 770    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
 771
 772    // Open and edit a buffer as both guests B and C.
 773    let buffer_b = project_b
 774        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 775        .await
 776        .unwrap();
 777    let buffer_c = project_c
 778        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 779        .await
 780        .unwrap();
 781    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
 782    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
 783
 784    // Open and edit that buffer as the host.
 785    let buffer_a = project_a
 786        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1"), cx))
 787        .await
 788        .unwrap();
 789
 790    buffer_a
 791        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ")
 792        .await;
 793    buffer_a.update(cx_a, |buf, cx| {
 794        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
 795    });
 796
 797    // Wait for edits to propagate
 798    buffer_a
 799        .condition(cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 800        .await;
 801    buffer_b
 802        .condition(cx_b, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 803        .await;
 804    buffer_c
 805        .condition(cx_c, |buf, _| buf.text() == "i-am-c, i-am-b, i-am-a")
 806        .await;
 807
 808    // Edit the buffer as the host and concurrently save as guest B.
 809    let save_b = buffer_b.update(cx_b, |buf, cx| buf.save(cx));
 810    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
 811    save_b.await.unwrap();
 812    assert_eq!(
 813        client_a.fs.load("/a/file1".as_ref()).await.unwrap(),
 814        "hi-a, i-am-c, i-am-b, i-am-a"
 815    );
 816    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
 817    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
 818    buffer_c.condition(cx_c, |buf, _| !buf.is_dirty()).await;
 819
 820    worktree_a.flush_fs_events(cx_a).await;
 821
 822    // Make changes on host's file system, see those changes on guest worktrees.
 823    client_a
 824        .fs
 825        .rename(
 826            "/a/file1".as_ref(),
 827            "/a/file1-renamed".as_ref(),
 828            Default::default(),
 829        )
 830        .await
 831        .unwrap();
 832
 833    client_a
 834        .fs
 835        .rename("/a/file2".as_ref(), "/a/file3".as_ref(), Default::default())
 836        .await
 837        .unwrap();
 838    client_a.fs.insert_file("/a/file4", "4".into()).await;
 839
 840    worktree_a
 841        .condition(cx_a, |tree, _| {
 842            tree.paths()
 843                .map(|p| p.to_string_lossy())
 844                .collect::<Vec<_>>()
 845                == ["file1-renamed", "file3", "file4"]
 846        })
 847        .await;
 848    worktree_b
 849        .condition(cx_b, |tree, _| {
 850            tree.paths()
 851                .map(|p| p.to_string_lossy())
 852                .collect::<Vec<_>>()
 853                == ["file1-renamed", "file3", "file4"]
 854        })
 855        .await;
 856    worktree_c
 857        .condition(cx_c, |tree, _| {
 858            tree.paths()
 859                .map(|p| p.to_string_lossy())
 860                .collect::<Vec<_>>()
 861                == ["file1-renamed", "file3", "file4"]
 862        })
 863        .await;
 864
 865    // Ensure buffer files are updated as well.
 866    buffer_a
 867        .condition(cx_a, |buf, _| {
 868            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 869        })
 870        .await;
 871    buffer_b
 872        .condition(cx_b, |buf, _| {
 873            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 874        })
 875        .await;
 876    buffer_c
 877        .condition(cx_c, |buf, _| {
 878            buf.file().unwrap().path().to_str() == Some("file1-renamed")
 879        })
 880        .await;
 881}
 882
 883#[gpui::test(iterations = 10)]
 884async fn test_fs_operations(
 885    executor: Arc<Deterministic>,
 886    cx_a: &mut TestAppContext,
 887    cx_b: &mut TestAppContext,
 888) {
 889    executor.forbid_parking();
 890    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
 891    let client_a = server.create_client(cx_a, "user_a").await;
 892    let client_b = server.create_client(cx_b, "user_b").await;
 893    server
 894        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
 895        .await;
 896
 897    client_a
 898        .fs
 899        .insert_tree(
 900            "/dir",
 901            json!({
 902                "a.txt": "a-contents",
 903                "b.txt": "b-contents",
 904            }),
 905        )
 906        .await;
 907    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 908    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 909
 910    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
 911    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
 912
 913    let entry = project_b
 914        .update(cx_b, |project, cx| {
 915            project
 916                .create_entry((worktree_id, "c.txt"), false, cx)
 917                .unwrap()
 918        })
 919        .await
 920        .unwrap();
 921    worktree_a.read_with(cx_a, |worktree, _| {
 922        assert_eq!(
 923            worktree
 924                .paths()
 925                .map(|p| p.to_string_lossy())
 926                .collect::<Vec<_>>(),
 927            ["a.txt", "b.txt", "c.txt"]
 928        );
 929    });
 930    worktree_b.read_with(cx_b, |worktree, _| {
 931        assert_eq!(
 932            worktree
 933                .paths()
 934                .map(|p| p.to_string_lossy())
 935                .collect::<Vec<_>>(),
 936            ["a.txt", "b.txt", "c.txt"]
 937        );
 938    });
 939
 940    project_b
 941        .update(cx_b, |project, cx| {
 942            project.rename_entry(entry.id, Path::new("d.txt"), cx)
 943        })
 944        .unwrap()
 945        .await
 946        .unwrap();
 947    worktree_a.read_with(cx_a, |worktree, _| {
 948        assert_eq!(
 949            worktree
 950                .paths()
 951                .map(|p| p.to_string_lossy())
 952                .collect::<Vec<_>>(),
 953            ["a.txt", "b.txt", "d.txt"]
 954        );
 955    });
 956    worktree_b.read_with(cx_b, |worktree, _| {
 957        assert_eq!(
 958            worktree
 959                .paths()
 960                .map(|p| p.to_string_lossy())
 961                .collect::<Vec<_>>(),
 962            ["a.txt", "b.txt", "d.txt"]
 963        );
 964    });
 965
 966    let dir_entry = project_b
 967        .update(cx_b, |project, cx| {
 968            project
 969                .create_entry((worktree_id, "DIR"), true, cx)
 970                .unwrap()
 971        })
 972        .await
 973        .unwrap();
 974    worktree_a.read_with(cx_a, |worktree, _| {
 975        assert_eq!(
 976            worktree
 977                .paths()
 978                .map(|p| p.to_string_lossy())
 979                .collect::<Vec<_>>(),
 980            ["DIR", "a.txt", "b.txt", "d.txt"]
 981        );
 982    });
 983    worktree_b.read_with(cx_b, |worktree, _| {
 984        assert_eq!(
 985            worktree
 986                .paths()
 987                .map(|p| p.to_string_lossy())
 988                .collect::<Vec<_>>(),
 989            ["DIR", "a.txt", "b.txt", "d.txt"]
 990        );
 991    });
 992
 993    project_b
 994        .update(cx_b, |project, cx| {
 995            project
 996                .create_entry((worktree_id, "DIR/e.txt"), false, cx)
 997                .unwrap()
 998        })
 999        .await
1000        .unwrap();
1001    project_b
1002        .update(cx_b, |project, cx| {
1003            project
1004                .create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
1005                .unwrap()
1006        })
1007        .await
1008        .unwrap();
1009    project_b
1010        .update(cx_b, |project, cx| {
1011            project
1012                .create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
1013                .unwrap()
1014        })
1015        .await
1016        .unwrap();
1017    worktree_a.read_with(cx_a, |worktree, _| {
1018        assert_eq!(
1019            worktree
1020                .paths()
1021                .map(|p| p.to_string_lossy())
1022                .collect::<Vec<_>>(),
1023            [
1024                "DIR",
1025                "DIR/SUBDIR",
1026                "DIR/SUBDIR/f.txt",
1027                "DIR/e.txt",
1028                "a.txt",
1029                "b.txt",
1030                "d.txt"
1031            ]
1032        );
1033    });
1034    worktree_b.read_with(cx_b, |worktree, _| {
1035        assert_eq!(
1036            worktree
1037                .paths()
1038                .map(|p| p.to_string_lossy())
1039                .collect::<Vec<_>>(),
1040            [
1041                "DIR",
1042                "DIR/SUBDIR",
1043                "DIR/SUBDIR/f.txt",
1044                "DIR/e.txt",
1045                "a.txt",
1046                "b.txt",
1047                "d.txt"
1048            ]
1049        );
1050    });
1051
1052    project_b
1053        .update(cx_b, |project, cx| {
1054            project
1055                .copy_entry(entry.id, Path::new("f.txt"), cx)
1056                .unwrap()
1057        })
1058        .await
1059        .unwrap();
1060    worktree_a.read_with(cx_a, |worktree, _| {
1061        assert_eq!(
1062            worktree
1063                .paths()
1064                .map(|p| p.to_string_lossy())
1065                .collect::<Vec<_>>(),
1066            [
1067                "DIR",
1068                "DIR/SUBDIR",
1069                "DIR/SUBDIR/f.txt",
1070                "DIR/e.txt",
1071                "a.txt",
1072                "b.txt",
1073                "d.txt",
1074                "f.txt"
1075            ]
1076        );
1077    });
1078    worktree_b.read_with(cx_b, |worktree, _| {
1079        assert_eq!(
1080            worktree
1081                .paths()
1082                .map(|p| p.to_string_lossy())
1083                .collect::<Vec<_>>(),
1084            [
1085                "DIR",
1086                "DIR/SUBDIR",
1087                "DIR/SUBDIR/f.txt",
1088                "DIR/e.txt",
1089                "a.txt",
1090                "b.txt",
1091                "d.txt",
1092                "f.txt"
1093            ]
1094        );
1095    });
1096
1097    project_b
1098        .update(cx_b, |project, cx| {
1099            project.delete_entry(dir_entry.id, cx).unwrap()
1100        })
1101        .await
1102        .unwrap();
1103    worktree_a.read_with(cx_a, |worktree, _| {
1104        assert_eq!(
1105            worktree
1106                .paths()
1107                .map(|p| p.to_string_lossy())
1108                .collect::<Vec<_>>(),
1109            ["a.txt", "b.txt", "d.txt", "f.txt"]
1110        );
1111    });
1112    worktree_b.read_with(cx_b, |worktree, _| {
1113        assert_eq!(
1114            worktree
1115                .paths()
1116                .map(|p| p.to_string_lossy())
1117                .collect::<Vec<_>>(),
1118            ["a.txt", "b.txt", "d.txt", "f.txt"]
1119        );
1120    });
1121
1122    project_b
1123        .update(cx_b, |project, cx| {
1124            project.delete_entry(entry.id, cx).unwrap()
1125        })
1126        .await
1127        .unwrap();
1128    worktree_a.read_with(cx_a, |worktree, _| {
1129        assert_eq!(
1130            worktree
1131                .paths()
1132                .map(|p| p.to_string_lossy())
1133                .collect::<Vec<_>>(),
1134            ["a.txt", "b.txt", "f.txt"]
1135        );
1136    });
1137    worktree_b.read_with(cx_b, |worktree, _| {
1138        assert_eq!(
1139            worktree
1140                .paths()
1141                .map(|p| p.to_string_lossy())
1142                .collect::<Vec<_>>(),
1143            ["a.txt", "b.txt", "f.txt"]
1144        );
1145    });
1146}
1147
1148#[gpui::test(iterations = 10)]
1149async fn test_buffer_conflict_after_save(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1150    cx_a.foreground().forbid_parking();
1151    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1152    let client_a = server.create_client(cx_a, "user_a").await;
1153    let client_b = server.create_client(cx_b, "user_b").await;
1154    server
1155        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1156        .await;
1157
1158    client_a
1159        .fs
1160        .insert_tree(
1161            "/dir",
1162            json!({
1163                "a.txt": "a-contents",
1164            }),
1165        )
1166        .await;
1167    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1168    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1169
1170    // Open a buffer as client B
1171    let buffer_b = project_b
1172        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1173        .await
1174        .unwrap();
1175
1176    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
1177    buffer_b.read_with(cx_b, |buf, _| {
1178        assert!(buf.is_dirty());
1179        assert!(!buf.has_conflict());
1180    });
1181
1182    buffer_b.update(cx_b, |buf, cx| buf.save(cx)).await.unwrap();
1183    buffer_b
1184        .condition(cx_b, |buffer_b, _| !buffer_b.is_dirty())
1185        .await;
1186    buffer_b.read_with(cx_b, |buf, _| {
1187        assert!(!buf.has_conflict());
1188    });
1189
1190    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
1191    buffer_b.read_with(cx_b, |buf, _| {
1192        assert!(buf.is_dirty());
1193        assert!(!buf.has_conflict());
1194    });
1195}
1196
1197#[gpui::test(iterations = 10)]
1198async fn test_buffer_reloading(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1199    cx_a.foreground().forbid_parking();
1200    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1201    let client_a = server.create_client(cx_a, "user_a").await;
1202    let client_b = server.create_client(cx_b, "user_b").await;
1203    server
1204        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1205        .await;
1206
1207    client_a
1208        .fs
1209        .insert_tree(
1210            "/dir",
1211            json!({
1212                "a.txt": "a\nb\nc",
1213            }),
1214        )
1215        .await;
1216    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1217    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1218
1219    // Open a buffer as client B
1220    let buffer_b = project_b
1221        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1222        .await
1223        .unwrap();
1224    buffer_b.read_with(cx_b, |buf, _| {
1225        assert!(!buf.is_dirty());
1226        assert!(!buf.has_conflict());
1227        assert_eq!(buf.line_ending(), LineEnding::Unix);
1228    });
1229
1230    let new_contents = Rope::from("d\ne\nf");
1231    client_a
1232        .fs
1233        .save("/dir/a.txt".as_ref(), &new_contents, LineEnding::Windows)
1234        .await
1235        .unwrap();
1236    buffer_b
1237        .condition(cx_b, |buf, _| {
1238            buf.text() == new_contents.to_string() && !buf.is_dirty()
1239        })
1240        .await;
1241    buffer_b.read_with(cx_b, |buf, _| {
1242        assert!(!buf.is_dirty());
1243        assert!(!buf.has_conflict());
1244        assert_eq!(buf.line_ending(), LineEnding::Windows);
1245    });
1246}
1247
1248#[gpui::test(iterations = 10)]
1249async fn test_editing_while_guest_opens_buffer(
1250    cx_a: &mut TestAppContext,
1251    cx_b: &mut TestAppContext,
1252) {
1253    cx_a.foreground().forbid_parking();
1254    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1255    let client_a = server.create_client(cx_a, "user_a").await;
1256    let client_b = server.create_client(cx_b, "user_b").await;
1257    server
1258        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1259        .await;
1260
1261    client_a
1262        .fs
1263        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
1264        .await;
1265    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1266    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1267
1268    // Open a buffer as client A
1269    let buffer_a = project_a
1270        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1271        .await
1272        .unwrap();
1273
1274    // Start opening the same buffer as client B
1275    let buffer_b = cx_b
1276        .background()
1277        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1278
1279    // Edit the buffer as client A while client B is still opening it.
1280    cx_b.background().simulate_random_delay().await;
1281    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
1282    cx_b.background().simulate_random_delay().await;
1283    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
1284
1285    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
1286    let buffer_b = buffer_b.await.unwrap();
1287    buffer_b.condition(cx_b, |buf, _| buf.text() == text).await;
1288}
1289
1290#[gpui::test(iterations = 10)]
1291async fn test_leaving_worktree_while_opening_buffer(
1292    cx_a: &mut TestAppContext,
1293    cx_b: &mut TestAppContext,
1294) {
1295    cx_a.foreground().forbid_parking();
1296    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1297    let client_a = server.create_client(cx_a, "user_a").await;
1298    let client_b = server.create_client(cx_b, "user_b").await;
1299    server
1300        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1301        .await;
1302
1303    client_a
1304        .fs
1305        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
1306        .await;
1307    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1308    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1309
1310    // See that a guest has joined as client A.
1311    project_a
1312        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1313        .await;
1314
1315    // Begin opening a buffer as client B, but leave the project before the open completes.
1316    let buffer_b = cx_b
1317        .background()
1318        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)));
1319    cx_b.update(|_| drop(project_b));
1320    drop(buffer_b);
1321
1322    // See that the guest has left.
1323    project_a
1324        .condition(cx_a, |p, _| p.collaborators().is_empty())
1325        .await;
1326}
1327
1328#[gpui::test(iterations = 10)]
1329async fn test_canceling_buffer_opening(
1330    deterministic: Arc<Deterministic>,
1331    cx_a: &mut TestAppContext,
1332    cx_b: &mut TestAppContext,
1333) {
1334    deterministic.forbid_parking();
1335
1336    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1337    let client_a = server.create_client(cx_a, "user_a").await;
1338    let client_b = server.create_client(cx_b, "user_b").await;
1339    server
1340        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1341        .await;
1342
1343    client_a
1344        .fs
1345        .insert_tree(
1346            "/dir",
1347            json!({
1348                "a.txt": "abc",
1349            }),
1350        )
1351        .await;
1352    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
1353    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1354
1355    let buffer_a = project_a
1356        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1357        .await
1358        .unwrap();
1359
1360    // Open a buffer as client B but cancel after a random amount of time.
1361    let buffer_b = project_b.update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx));
1362    deterministic.simulate_random_delay().await;
1363    drop(buffer_b);
1364
1365    // Try opening the same buffer again as client B, and ensure we can
1366    // still do it despite the cancellation above.
1367    let buffer_b = project_b
1368        .update(cx_b, |p, cx| p.open_buffer_by_id(buffer_a.id() as u64, cx))
1369        .await
1370        .unwrap();
1371    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
1372}
1373
1374#[gpui::test(iterations = 10)]
1375async fn test_leaving_project(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1376    cx_a.foreground().forbid_parking();
1377    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1378    let client_a = server.create_client(cx_a, "user_a").await;
1379    let client_b = server.create_client(cx_b, "user_b").await;
1380    server
1381        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1382        .await;
1383
1384    client_a
1385        .fs
1386        .insert_tree(
1387            "/a",
1388            json!({
1389                "a.txt": "a-contents",
1390                "b.txt": "b-contents",
1391            }),
1392        )
1393        .await;
1394    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1395    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1396
1397    // Client A sees that a guest has joined.
1398    project_a
1399        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1400        .await;
1401
1402    // Drop client B's connection and ensure client A observes client B leaving the project.
1403    client_b.disconnect(&cx_b.to_async()).unwrap();
1404    project_a
1405        .condition(cx_a, |p, _| p.collaborators().is_empty())
1406        .await;
1407
1408    // Rejoin the project as client B
1409    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1410
1411    // Client A sees that a guest has re-joined.
1412    project_a
1413        .condition(cx_a, |p, _| p.collaborators().len() == 1)
1414        .await;
1415
1416    // Simulate connection loss for client B and ensure client A observes client B leaving the project.
1417    client_b.wait_for_current_user(cx_b).await;
1418    server.disconnect_client(client_b.current_user_id(cx_b));
1419    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
1420    project_a
1421        .condition(cx_a, |p, _| p.collaborators().is_empty())
1422        .await;
1423}
1424
1425#[gpui::test(iterations = 10)]
1426async fn test_collaborating_with_diagnostics(
1427    deterministic: Arc<Deterministic>,
1428    cx_a: &mut TestAppContext,
1429    cx_b: &mut TestAppContext,
1430    cx_c: &mut TestAppContext,
1431) {
1432    deterministic.forbid_parking();
1433    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1434    let client_a = server.create_client(cx_a, "user_a").await;
1435    let client_b = server.create_client(cx_b, "user_b").await;
1436    let client_c = server.create_client(cx_c, "user_c").await;
1437    server
1438        .make_contacts(vec![
1439            (&client_a, cx_a),
1440            (&client_b, cx_b),
1441            (&client_c, cx_c),
1442        ])
1443        .await;
1444
1445    // Set up a fake language server.
1446    let mut language = Language::new(
1447        LanguageConfig {
1448            name: "Rust".into(),
1449            path_suffixes: vec!["rs".to_string()],
1450            ..Default::default()
1451        },
1452        Some(tree_sitter_rust::language()),
1453    );
1454    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
1455    client_a.language_registry.add(Arc::new(language));
1456
1457    // Share a project as client A
1458    client_a
1459        .fs
1460        .insert_tree(
1461            "/a",
1462            json!({
1463                "a.rs": "let one = two",
1464                "other.rs": "",
1465            }),
1466        )
1467        .await;
1468    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1469    let project_id = project_a.update(cx_a, |p, _| p.next_remote_id()).await;
1470
1471    // Cause the language server to start.
1472    let _buffer = cx_a
1473        .background()
1474        .spawn(project_a.update(cx_a, |project, cx| {
1475            project.open_buffer(
1476                ProjectPath {
1477                    worktree_id,
1478                    path: Path::new("other.rs").into(),
1479                },
1480                cx,
1481            )
1482        }))
1483        .await
1484        .unwrap();
1485
1486    // Join the worktree as client B.
1487    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1488
1489    // Simulate a language server reporting errors for a file.
1490    let mut fake_language_server = fake_language_servers.next().await.unwrap();
1491    fake_language_server
1492        .receive_notification::<lsp::notification::DidOpenTextDocument>()
1493        .await;
1494    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1495        lsp::PublishDiagnosticsParams {
1496            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1497            version: None,
1498            diagnostics: vec![lsp::Diagnostic {
1499                severity: Some(lsp::DiagnosticSeverity::ERROR),
1500                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1501                message: "message 1".to_string(),
1502                ..Default::default()
1503            }],
1504        },
1505    );
1506
1507    // Wait for server to see the diagnostics update.
1508    deterministic.run_until_parked();
1509    {
1510        let store = server.store.lock().await;
1511        let project = store.project(ProjectId::from_proto(project_id)).unwrap();
1512        let worktree = project.worktrees.get(&worktree_id.to_proto()).unwrap();
1513        assert!(!worktree.diagnostic_summaries.is_empty());
1514    }
1515
1516    // Ensure client B observes the new diagnostics.
1517    project_b.read_with(cx_b, |project, cx| {
1518        assert_eq!(
1519            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1520            &[(
1521                ProjectPath {
1522                    worktree_id,
1523                    path: Arc::from(Path::new("a.rs")),
1524                },
1525                DiagnosticSummary {
1526                    error_count: 1,
1527                    warning_count: 0,
1528                    ..Default::default()
1529                },
1530            )]
1531        )
1532    });
1533
1534    // Join project as client C and observe the diagnostics.
1535    let project_c = client_c.build_remote_project(&project_a, cx_a, cx_c).await;
1536    deterministic.run_until_parked();
1537    project_c.read_with(cx_c, |project, cx| {
1538        assert_eq!(
1539            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1540            &[(
1541                ProjectPath {
1542                    worktree_id,
1543                    path: Arc::from(Path::new("a.rs")),
1544                },
1545                DiagnosticSummary {
1546                    error_count: 1,
1547                    warning_count: 0,
1548                    ..Default::default()
1549                },
1550            )]
1551        )
1552    });
1553
1554    // Simulate a language server reporting more errors for a file.
1555    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1556        lsp::PublishDiagnosticsParams {
1557            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1558            version: None,
1559            diagnostics: vec![
1560                lsp::Diagnostic {
1561                    severity: Some(lsp::DiagnosticSeverity::ERROR),
1562                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
1563                    message: "message 1".to_string(),
1564                    ..Default::default()
1565                },
1566                lsp::Diagnostic {
1567                    severity: Some(lsp::DiagnosticSeverity::WARNING),
1568                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
1569                    message: "message 2".to_string(),
1570                    ..Default::default()
1571                },
1572            ],
1573        },
1574    );
1575
1576    // Clients B and C get the updated summaries
1577    deterministic.run_until_parked();
1578    project_b.read_with(cx_b, |project, cx| {
1579        assert_eq!(
1580            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1581            [(
1582                ProjectPath {
1583                    worktree_id,
1584                    path: Arc::from(Path::new("a.rs")),
1585                },
1586                DiagnosticSummary {
1587                    error_count: 1,
1588                    warning_count: 1,
1589                    ..Default::default()
1590                },
1591            )]
1592        );
1593    });
1594    project_c.read_with(cx_c, |project, cx| {
1595        assert_eq!(
1596            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
1597            [(
1598                ProjectPath {
1599                    worktree_id,
1600                    path: Arc::from(Path::new("a.rs")),
1601                },
1602                DiagnosticSummary {
1603                    error_count: 1,
1604                    warning_count: 1,
1605                    ..Default::default()
1606                },
1607            )]
1608        );
1609    });
1610
1611    // Open the file with the errors on client B. They should be present.
1612    let buffer_b = cx_b
1613        .background()
1614        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1615        .await
1616        .unwrap();
1617
1618    buffer_b.read_with(cx_b, |buffer, _| {
1619        assert_eq!(
1620            buffer
1621                .snapshot()
1622                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
1623                .collect::<Vec<_>>(),
1624            &[
1625                DiagnosticEntry {
1626                    range: Point::new(0, 4)..Point::new(0, 7),
1627                    diagnostic: Diagnostic {
1628                        group_id: 1,
1629                        message: "message 1".to_string(),
1630                        severity: lsp::DiagnosticSeverity::ERROR,
1631                        is_primary: true,
1632                        ..Default::default()
1633                    }
1634                },
1635                DiagnosticEntry {
1636                    range: Point::new(0, 10)..Point::new(0, 13),
1637                    diagnostic: Diagnostic {
1638                        group_id: 2,
1639                        severity: lsp::DiagnosticSeverity::WARNING,
1640                        message: "message 2".to_string(),
1641                        is_primary: true,
1642                        ..Default::default()
1643                    }
1644                }
1645            ]
1646        );
1647    });
1648
1649    // Simulate a language server reporting no errors for a file.
1650    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
1651        lsp::PublishDiagnosticsParams {
1652            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
1653            version: None,
1654            diagnostics: vec![],
1655        },
1656    );
1657    deterministic.run_until_parked();
1658    project_a.read_with(cx_a, |project, cx| {
1659        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1660    });
1661    project_b.read_with(cx_b, |project, cx| {
1662        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1663    });
1664    project_c.read_with(cx_c, |project, cx| {
1665        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
1666    });
1667}
1668
1669#[gpui::test(iterations = 10)]
1670async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1671    cx_a.foreground().forbid_parking();
1672    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1673    let client_a = server.create_client(cx_a, "user_a").await;
1674    let client_b = server.create_client(cx_b, "user_b").await;
1675    server
1676        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1677        .await;
1678
1679    // Set up a fake language server.
1680    let mut language = Language::new(
1681        LanguageConfig {
1682            name: "Rust".into(),
1683            path_suffixes: vec!["rs".to_string()],
1684            ..Default::default()
1685        },
1686        Some(tree_sitter_rust::language()),
1687    );
1688    let mut fake_language_servers = language
1689        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1690            capabilities: lsp::ServerCapabilities {
1691                completion_provider: Some(lsp::CompletionOptions {
1692                    trigger_characters: Some(vec![".".to_string()]),
1693                    ..Default::default()
1694                }),
1695                ..Default::default()
1696            },
1697            ..Default::default()
1698        }))
1699        .await;
1700    client_a.language_registry.add(Arc::new(language));
1701
1702    client_a
1703        .fs
1704        .insert_tree(
1705            "/a",
1706            json!({
1707                "main.rs": "fn main() { a }",
1708                "other.rs": "",
1709            }),
1710        )
1711        .await;
1712    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1713    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1714
1715    // Open a file in an editor as the guest.
1716    let buffer_b = project_b
1717        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1718        .await
1719        .unwrap();
1720    let (_, window_b) = cx_b.add_window(|_| EmptyView);
1721    let editor_b = cx_b.add_view(&window_b, |cx| {
1722        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
1723    });
1724
1725    let fake_language_server = fake_language_servers.next().await.unwrap();
1726    buffer_b
1727        .condition(cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
1728        .await;
1729
1730    // Type a completion trigger character as the guest.
1731    editor_b.update(cx_b, |editor, cx| {
1732        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1733        editor.handle_input(".", cx);
1734        cx.focus(&editor_b);
1735    });
1736
1737    // Receive a completion request as the host's language server.
1738    // Return some completions from the host's language server.
1739    cx_a.foreground().start_waiting();
1740    fake_language_server
1741        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
1742            assert_eq!(
1743                params.text_document_position.text_document.uri,
1744                lsp::Url::from_file_path("/a/main.rs").unwrap(),
1745            );
1746            assert_eq!(
1747                params.text_document_position.position,
1748                lsp::Position::new(0, 14),
1749            );
1750
1751            Ok(Some(lsp::CompletionResponse::Array(vec![
1752                lsp::CompletionItem {
1753                    label: "first_method(…)".into(),
1754                    detail: Some("fn(&mut self, B) -> C".into()),
1755                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1756                        new_text: "first_method($1)".to_string(),
1757                        range: lsp::Range::new(
1758                            lsp::Position::new(0, 14),
1759                            lsp::Position::new(0, 14),
1760                        ),
1761                    })),
1762                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1763                    ..Default::default()
1764                },
1765                lsp::CompletionItem {
1766                    label: "second_method(…)".into(),
1767                    detail: Some("fn(&mut self, C) -> D<E>".into()),
1768                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1769                        new_text: "second_method()".to_string(),
1770                        range: lsp::Range::new(
1771                            lsp::Position::new(0, 14),
1772                            lsp::Position::new(0, 14),
1773                        ),
1774                    })),
1775                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1776                    ..Default::default()
1777                },
1778            ])))
1779        })
1780        .next()
1781        .await
1782        .unwrap();
1783    cx_a.foreground().finish_waiting();
1784
1785    // Open the buffer on the host.
1786    let buffer_a = project_a
1787        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1788        .await
1789        .unwrap();
1790    buffer_a
1791        .condition(cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
1792        .await;
1793
1794    // Confirm a completion on the guest.
1795    editor_b
1796        .condition(cx_b, |editor, _| editor.context_menu_visible())
1797        .await;
1798    editor_b.update(cx_b, |editor, cx| {
1799        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
1800        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
1801    });
1802
1803    // Return a resolved completion from the host's language server.
1804    // The resolved completion has an additional text edit.
1805    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
1806        |params, _| async move {
1807            assert_eq!(params.label, "first_method(…)");
1808            Ok(lsp::CompletionItem {
1809                label: "first_method(…)".into(),
1810                detail: Some("fn(&mut self, B) -> C".into()),
1811                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
1812                    new_text: "first_method($1)".to_string(),
1813                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1814                })),
1815                additional_text_edits: Some(vec![lsp::TextEdit {
1816                    new_text: "use d::SomeTrait;\n".to_string(),
1817                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
1818                }]),
1819                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
1820                ..Default::default()
1821            })
1822        },
1823    );
1824
1825    // The additional edit is applied.
1826    buffer_a
1827        .condition(cx_a, |buffer, _| {
1828            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1829        })
1830        .await;
1831    buffer_b
1832        .condition(cx_b, |buffer, _| {
1833            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
1834        })
1835        .await;
1836}
1837
1838#[gpui::test(iterations = 10)]
1839async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1840    cx_a.foreground().forbid_parking();
1841    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1842    let client_a = server.create_client(cx_a, "user_a").await;
1843    let client_b = server.create_client(cx_b, "user_b").await;
1844    server
1845        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1846        .await;
1847
1848    client_a
1849        .fs
1850        .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
1851        .await;
1852    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1853    let buffer_a = project_a
1854        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
1855        .await
1856        .unwrap();
1857
1858    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1859
1860    let buffer_b = cx_b
1861        .background()
1862        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1863        .await
1864        .unwrap();
1865    buffer_b.update(cx_b, |buffer, cx| {
1866        buffer.edit([(4..7, "six")], None, cx);
1867        buffer.edit([(10..11, "6")], None, cx);
1868        assert_eq!(buffer.text(), "let six = 6;");
1869        assert!(buffer.is_dirty());
1870        assert!(!buffer.has_conflict());
1871    });
1872    buffer_a
1873        .condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
1874        .await;
1875
1876    client_a
1877        .fs
1878        .save(
1879            "/a/a.rs".as_ref(),
1880            &Rope::from("let seven = 7;"),
1881            LineEnding::Unix,
1882        )
1883        .await
1884        .unwrap();
1885    buffer_a
1886        .condition(cx_a, |buffer, _| buffer.has_conflict())
1887        .await;
1888    buffer_b
1889        .condition(cx_b, |buffer, _| buffer.has_conflict())
1890        .await;
1891
1892    project_b
1893        .update(cx_b, |project, cx| {
1894            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
1895        })
1896        .await
1897        .unwrap();
1898    buffer_a.read_with(cx_a, |buffer, _| {
1899        assert_eq!(buffer.text(), "let seven = 7;");
1900        assert!(!buffer.is_dirty());
1901        assert!(!buffer.has_conflict());
1902    });
1903    buffer_b.read_with(cx_b, |buffer, _| {
1904        assert_eq!(buffer.text(), "let seven = 7;");
1905        assert!(!buffer.is_dirty());
1906        assert!(!buffer.has_conflict());
1907    });
1908
1909    buffer_a.update(cx_a, |buffer, cx| {
1910        // Undoing on the host is a no-op when the reload was initiated by the guest.
1911        buffer.undo(cx);
1912        assert_eq!(buffer.text(), "let seven = 7;");
1913        assert!(!buffer.is_dirty());
1914        assert!(!buffer.has_conflict());
1915    });
1916    buffer_b.update(cx_b, |buffer, cx| {
1917        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
1918        buffer.undo(cx);
1919        assert_eq!(buffer.text(), "let six = 6;");
1920        assert!(buffer.is_dirty());
1921        assert!(!buffer.has_conflict());
1922    });
1923}
1924
1925#[gpui::test(iterations = 10)]
1926async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
1927    use project::FormatTrigger;
1928
1929    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
1930    let client_a = server.create_client(cx_a, "user_a").await;
1931    let client_b = server.create_client(cx_b, "user_b").await;
1932    server
1933        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
1934        .await;
1935
1936    // Set up a fake language server.
1937    let mut language = Language::new(
1938        LanguageConfig {
1939            name: "Rust".into(),
1940            path_suffixes: vec!["rs".to_string()],
1941            ..Default::default()
1942        },
1943        Some(tree_sitter_rust::language()),
1944    );
1945    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
1946    client_a.language_registry.add(Arc::new(language));
1947
1948    // Here we insert a fake tree with a directory that exists on disk. This is needed
1949    // because later we'll invoke a command, which requires passing a working directory
1950    // that points to a valid location on disk.
1951    let directory = env::current_dir().unwrap();
1952    client_a
1953        .fs
1954        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
1955        .await;
1956    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
1957    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
1958
1959    let buffer_b = cx_b
1960        .background()
1961        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
1962        .await
1963        .unwrap();
1964
1965    let fake_language_server = fake_language_servers.next().await.unwrap();
1966    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
1967        Ok(Some(vec![
1968            lsp::TextEdit {
1969                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
1970                new_text: "h".to_string(),
1971            },
1972            lsp::TextEdit {
1973                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
1974                new_text: "y".to_string(),
1975            },
1976        ]))
1977    });
1978
1979    project_b
1980        .update(cx_b, |project, cx| {
1981            project.format(
1982                HashSet::from_iter([buffer_b.clone()]),
1983                true,
1984                FormatTrigger::Save,
1985                cx,
1986            )
1987        })
1988        .await
1989        .unwrap();
1990    assert_eq!(
1991        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
1992        "let honey = \"two\""
1993    );
1994
1995    // Ensure buffer can be formatted using an external command. Notice how the
1996    // host's configuration is honored as opposed to using the guest's settings.
1997    cx_a.update(|cx| {
1998        cx.update_global(|settings: &mut Settings, _| {
1999            settings.editor_defaults.formatter = Some(Formatter::External {
2000                command: "awk".to_string(),
2001                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
2002            });
2003        });
2004    });
2005    project_b
2006        .update(cx_b, |project, cx| {
2007            project.format(
2008                HashSet::from_iter([buffer_b.clone()]),
2009                true,
2010                FormatTrigger::Save,
2011                cx,
2012            )
2013        })
2014        .await
2015        .unwrap();
2016    assert_eq!(
2017        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2018        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
2019    );
2020}
2021
2022#[gpui::test(iterations = 10)]
2023async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2024    cx_a.foreground().forbid_parking();
2025    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2026    let client_a = server.create_client(cx_a, "user_a").await;
2027    let client_b = server.create_client(cx_b, "user_b").await;
2028    server
2029        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2030        .await;
2031
2032    // Set up a fake language server.
2033    let mut language = Language::new(
2034        LanguageConfig {
2035            name: "Rust".into(),
2036            path_suffixes: vec!["rs".to_string()],
2037            ..Default::default()
2038        },
2039        Some(tree_sitter_rust::language()),
2040    );
2041    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2042    client_a.language_registry.add(Arc::new(language));
2043
2044    client_a
2045        .fs
2046        .insert_tree(
2047            "/root",
2048            json!({
2049                "dir-1": {
2050                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
2051                },
2052                "dir-2": {
2053                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
2054                    "c.rs": "type T2 = usize;",
2055                }
2056            }),
2057        )
2058        .await;
2059    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2060    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2061
2062    // Open the file on client B.
2063    let buffer_b = cx_b
2064        .background()
2065        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2066        .await
2067        .unwrap();
2068
2069    // Request the definition of a symbol as the guest.
2070    let fake_language_server = fake_language_servers.next().await.unwrap();
2071    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2072        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2073            lsp::Location::new(
2074                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2075                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2076            ),
2077        )))
2078    });
2079
2080    let definitions_1 = project_b
2081        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
2082        .await
2083        .unwrap();
2084    cx_b.read(|cx| {
2085        assert_eq!(definitions_1.len(), 1);
2086        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2087        let target_buffer = definitions_1[0].target.buffer.read(cx);
2088        assert_eq!(
2089            target_buffer.text(),
2090            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
2091        );
2092        assert_eq!(
2093            definitions_1[0].target.range.to_point(target_buffer),
2094            Point::new(0, 6)..Point::new(0, 9)
2095        );
2096    });
2097
2098    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
2099    // the previous call to `definition`.
2100    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2101        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2102            lsp::Location::new(
2103                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2104                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
2105            ),
2106        )))
2107    });
2108
2109    let definitions_2 = project_b
2110        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
2111        .await
2112        .unwrap();
2113    cx_b.read(|cx| {
2114        assert_eq!(definitions_2.len(), 1);
2115        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2116        let target_buffer = definitions_2[0].target.buffer.read(cx);
2117        assert_eq!(
2118            target_buffer.text(),
2119            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
2120        );
2121        assert_eq!(
2122            definitions_2[0].target.range.to_point(target_buffer),
2123            Point::new(1, 6)..Point::new(1, 11)
2124        );
2125    });
2126    assert_eq!(
2127        definitions_1[0].target.buffer,
2128        definitions_2[0].target.buffer
2129    );
2130
2131    fake_language_server.handle_request::<lsp::request::GotoTypeDefinition, _, _>(
2132        |req, _| async move {
2133            assert_eq!(
2134                req.text_document_position_params.position,
2135                lsp::Position::new(0, 7)
2136            );
2137            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2138                lsp::Location::new(
2139                    lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
2140                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
2141                ),
2142            )))
2143        },
2144    );
2145
2146    let type_definitions = project_b
2147        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
2148        .await
2149        .unwrap();
2150    cx_b.read(|cx| {
2151        assert_eq!(type_definitions.len(), 1);
2152        let target_buffer = type_definitions[0].target.buffer.read(cx);
2153        assert_eq!(target_buffer.text(), "type T2 = usize;");
2154        assert_eq!(
2155            type_definitions[0].target.range.to_point(target_buffer),
2156            Point::new(0, 5)..Point::new(0, 7)
2157        );
2158    });
2159}
2160
2161#[gpui::test(iterations = 10)]
2162async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2163    cx_a.foreground().forbid_parking();
2164    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2165    let client_a = server.create_client(cx_a, "user_a").await;
2166    let client_b = server.create_client(cx_b, "user_b").await;
2167    server
2168        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2169        .await;
2170
2171    // Set up a fake language server.
2172    let mut language = Language::new(
2173        LanguageConfig {
2174            name: "Rust".into(),
2175            path_suffixes: vec!["rs".to_string()],
2176            ..Default::default()
2177        },
2178        Some(tree_sitter_rust::language()),
2179    );
2180    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2181    client_a.language_registry.add(Arc::new(language));
2182
2183    client_a
2184        .fs
2185        .insert_tree(
2186            "/root",
2187            json!({
2188                "dir-1": {
2189                    "one.rs": "const ONE: usize = 1;",
2190                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
2191                },
2192                "dir-2": {
2193                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
2194                }
2195            }),
2196        )
2197        .await;
2198    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2199    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2200
2201    // Open the file on client B.
2202    let buffer_b = cx_b
2203        .background()
2204        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2205        .await
2206        .unwrap();
2207
2208    // Request references to a symbol as the guest.
2209    let fake_language_server = fake_language_servers.next().await.unwrap();
2210    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
2211        assert_eq!(
2212            params.text_document_position.text_document.uri.as_str(),
2213            "file:///root/dir-1/one.rs"
2214        );
2215        Ok(Some(vec![
2216            lsp::Location {
2217                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2218                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
2219            },
2220            lsp::Location {
2221                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2222                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
2223            },
2224            lsp::Location {
2225                uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
2226                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
2227            },
2228        ]))
2229    });
2230
2231    let references = project_b
2232        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
2233        .await
2234        .unwrap();
2235    cx_b.read(|cx| {
2236        assert_eq!(references.len(), 3);
2237        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2238
2239        let two_buffer = references[0].buffer.read(cx);
2240        let three_buffer = references[2].buffer.read(cx);
2241        assert_eq!(
2242            two_buffer.file().unwrap().path().as_ref(),
2243            Path::new("two.rs")
2244        );
2245        assert_eq!(references[1].buffer, references[0].buffer);
2246        assert_eq!(
2247            three_buffer.file().unwrap().full_path(cx),
2248            Path::new("three.rs")
2249        );
2250
2251        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
2252        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
2253        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
2254    });
2255}
2256
2257#[gpui::test(iterations = 10)]
2258async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2259    cx_a.foreground().forbid_parking();
2260    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2261    let client_a = server.create_client(cx_a, "user_a").await;
2262    let client_b = server.create_client(cx_b, "user_b").await;
2263    server
2264        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2265        .await;
2266
2267    client_a
2268        .fs
2269        .insert_tree(
2270            "/root",
2271            json!({
2272                "dir-1": {
2273                    "a": "hello world",
2274                    "b": "goodnight moon",
2275                    "c": "a world of goo",
2276                    "d": "world champion of clown world",
2277                },
2278                "dir-2": {
2279                    "e": "disney world is fun",
2280                }
2281            }),
2282        )
2283        .await;
2284    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
2285    let (worktree_2, _) = project_a
2286        .update(cx_a, |p, cx| {
2287            p.find_or_create_local_worktree("/root/dir-2", true, cx)
2288        })
2289        .await
2290        .unwrap();
2291    worktree_2
2292        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
2293        .await;
2294
2295    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2296
2297    // Perform a search as the guest.
2298    let results = project_b
2299        .update(cx_b, |project, cx| {
2300            project.search(SearchQuery::text("world", false, false), cx)
2301        })
2302        .await
2303        .unwrap();
2304
2305    let mut ranges_by_path = results
2306        .into_iter()
2307        .map(|(buffer, ranges)| {
2308            buffer.read_with(cx_b, |buffer, cx| {
2309                let path = buffer.file().unwrap().full_path(cx);
2310                let offset_ranges = ranges
2311                    .into_iter()
2312                    .map(|range| range.to_offset(buffer))
2313                    .collect::<Vec<_>>();
2314                (path, offset_ranges)
2315            })
2316        })
2317        .collect::<Vec<_>>();
2318    ranges_by_path.sort_by_key(|(path, _)| path.clone());
2319
2320    assert_eq!(
2321        ranges_by_path,
2322        &[
2323            (PathBuf::from("dir-1/a"), vec![6..11]),
2324            (PathBuf::from("dir-1/c"), vec![2..7]),
2325            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
2326            (PathBuf::from("dir-2/e"), vec![7..12]),
2327        ]
2328    );
2329}
2330
2331#[gpui::test(iterations = 10)]
2332async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2333    cx_a.foreground().forbid_parking();
2334    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2335    let client_a = server.create_client(cx_a, "user_a").await;
2336    let client_b = server.create_client(cx_b, "user_b").await;
2337    server
2338        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2339        .await;
2340
2341    client_a
2342        .fs
2343        .insert_tree(
2344            "/root-1",
2345            json!({
2346                "main.rs": "fn double(number: i32) -> i32 { number + number }",
2347            }),
2348        )
2349        .await;
2350
2351    // Set up a fake language server.
2352    let mut language = Language::new(
2353        LanguageConfig {
2354            name: "Rust".into(),
2355            path_suffixes: vec!["rs".to_string()],
2356            ..Default::default()
2357        },
2358        Some(tree_sitter_rust::language()),
2359    );
2360    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2361    client_a.language_registry.add(Arc::new(language));
2362
2363    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2364    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2365
2366    // Open the file on client B.
2367    let buffer_b = cx_b
2368        .background()
2369        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2370        .await
2371        .unwrap();
2372
2373    // Request document highlights as the guest.
2374    let fake_language_server = fake_language_servers.next().await.unwrap();
2375    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
2376        |params, _| async move {
2377            assert_eq!(
2378                params
2379                    .text_document_position_params
2380                    .text_document
2381                    .uri
2382                    .as_str(),
2383                "file:///root-1/main.rs"
2384            );
2385            assert_eq!(
2386                params.text_document_position_params.position,
2387                lsp::Position::new(0, 34)
2388            );
2389            Ok(Some(vec![
2390                lsp::DocumentHighlight {
2391                    kind: Some(lsp::DocumentHighlightKind::WRITE),
2392                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
2393                },
2394                lsp::DocumentHighlight {
2395                    kind: Some(lsp::DocumentHighlightKind::READ),
2396                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
2397                },
2398                lsp::DocumentHighlight {
2399                    kind: Some(lsp::DocumentHighlightKind::READ),
2400                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
2401                },
2402            ]))
2403        },
2404    );
2405
2406    let highlights = project_b
2407        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
2408        .await
2409        .unwrap();
2410    buffer_b.read_with(cx_b, |buffer, _| {
2411        let snapshot = buffer.snapshot();
2412
2413        let highlights = highlights
2414            .into_iter()
2415            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
2416            .collect::<Vec<_>>();
2417        assert_eq!(
2418            highlights,
2419            &[
2420                (lsp::DocumentHighlightKind::WRITE, 10..16),
2421                (lsp::DocumentHighlightKind::READ, 32..38),
2422                (lsp::DocumentHighlightKind::READ, 41..47)
2423            ]
2424        )
2425    });
2426}
2427
2428#[gpui::test(iterations = 10)]
2429async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2430    cx_a.foreground().forbid_parking();
2431    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2432    let client_a = server.create_client(cx_a, "user_a").await;
2433    let client_b = server.create_client(cx_b, "user_b").await;
2434    server
2435        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2436        .await;
2437
2438    client_a
2439        .fs
2440        .insert_tree(
2441            "/root-1",
2442            json!({
2443                "main.rs": "use std::collections::HashMap;",
2444            }),
2445        )
2446        .await;
2447
2448    // Set up a fake language server.
2449    let mut language = Language::new(
2450        LanguageConfig {
2451            name: "Rust".into(),
2452            path_suffixes: vec!["rs".to_string()],
2453            ..Default::default()
2454        },
2455        Some(tree_sitter_rust::language()),
2456    );
2457    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2458    client_a.language_registry.add(Arc::new(language));
2459
2460    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
2461    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2462
2463    // Open the file as the guest
2464    let buffer_b = cx_b
2465        .background()
2466        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
2467        .await
2468        .unwrap();
2469
2470    // Request hover information as the guest.
2471    let fake_language_server = fake_language_servers.next().await.unwrap();
2472    fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
2473        |params, _| async move {
2474            assert_eq!(
2475                params
2476                    .text_document_position_params
2477                    .text_document
2478                    .uri
2479                    .as_str(),
2480                "file:///root-1/main.rs"
2481            );
2482            assert_eq!(
2483                params.text_document_position_params.position,
2484                lsp::Position::new(0, 22)
2485            );
2486            Ok(Some(lsp::Hover {
2487                contents: lsp::HoverContents::Array(vec![
2488                    lsp::MarkedString::String("Test hover content.".to_string()),
2489                    lsp::MarkedString::LanguageString(lsp::LanguageString {
2490                        language: "Rust".to_string(),
2491                        value: "let foo = 42;".to_string(),
2492                    }),
2493                ]),
2494                range: Some(lsp::Range::new(
2495                    lsp::Position::new(0, 22),
2496                    lsp::Position::new(0, 29),
2497                )),
2498            }))
2499        },
2500    );
2501
2502    let hover_info = project_b
2503        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
2504        .await
2505        .unwrap()
2506        .unwrap();
2507    buffer_b.read_with(cx_b, |buffer, _| {
2508        let snapshot = buffer.snapshot();
2509        assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
2510        assert_eq!(
2511            hover_info.contents,
2512            vec![
2513                project::HoverBlock {
2514                    text: "Test hover content.".to_string(),
2515                    language: None,
2516                },
2517                project::HoverBlock {
2518                    text: "let foo = 42;".to_string(),
2519                    language: Some("Rust".to_string()),
2520                }
2521            ]
2522        );
2523    });
2524}
2525
2526#[gpui::test(iterations = 10)]
2527async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2528    cx_a.foreground().forbid_parking();
2529    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2530    let client_a = server.create_client(cx_a, "user_a").await;
2531    let client_b = server.create_client(cx_b, "user_b").await;
2532    server
2533        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2534        .await;
2535
2536    // Set up a fake language server.
2537    let mut language = Language::new(
2538        LanguageConfig {
2539            name: "Rust".into(),
2540            path_suffixes: vec!["rs".to_string()],
2541            ..Default::default()
2542        },
2543        Some(tree_sitter_rust::language()),
2544    );
2545    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2546    client_a.language_registry.add(Arc::new(language));
2547
2548    client_a
2549        .fs
2550        .insert_tree(
2551            "/code",
2552            json!({
2553                "crate-1": {
2554                    "one.rs": "const ONE: usize = 1;",
2555                },
2556                "crate-2": {
2557                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
2558                },
2559                "private": {
2560                    "passwords.txt": "the-password",
2561                }
2562            }),
2563        )
2564        .await;
2565    let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
2566    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2567
2568    // Cause the language server to start.
2569    let _buffer = cx_b
2570        .background()
2571        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2572        .await
2573        .unwrap();
2574
2575    let fake_language_server = fake_language_servers.next().await.unwrap();
2576    fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
2577        #[allow(deprecated)]
2578        Ok(Some(vec![lsp::SymbolInformation {
2579            name: "TWO".into(),
2580            location: lsp::Location {
2581                uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
2582                range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2583            },
2584            kind: lsp::SymbolKind::CONSTANT,
2585            tags: None,
2586            container_name: None,
2587            deprecated: None,
2588        }]))
2589    });
2590
2591    // Request the definition of a symbol as the guest.
2592    let symbols = project_b
2593        .update(cx_b, |p, cx| p.symbols("two", cx))
2594        .await
2595        .unwrap();
2596    assert_eq!(symbols.len(), 1);
2597    assert_eq!(symbols[0].name, "TWO");
2598
2599    // Open one of the returned symbols.
2600    let buffer_b_2 = project_b
2601        .update(cx_b, |project, cx| {
2602            project.open_buffer_for_symbol(&symbols[0], cx)
2603        })
2604        .await
2605        .unwrap();
2606    buffer_b_2.read_with(cx_b, |buffer, _| {
2607        assert_eq!(
2608            buffer.file().unwrap().path().as_ref(),
2609            Path::new("../crate-2/two.rs")
2610        );
2611    });
2612
2613    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
2614    let mut fake_symbol = symbols[0].clone();
2615    fake_symbol.path.path = Path::new("/code/secrets").into();
2616    let error = project_b
2617        .update(cx_b, |project, cx| {
2618            project.open_buffer_for_symbol(&fake_symbol, cx)
2619        })
2620        .await
2621        .unwrap_err();
2622    assert!(error.to_string().contains("invalid symbol signature"));
2623}
2624
2625#[gpui::test(iterations = 10)]
2626async fn test_open_buffer_while_getting_definition_pointing_to_it(
2627    cx_a: &mut TestAppContext,
2628    cx_b: &mut TestAppContext,
2629    mut rng: StdRng,
2630) {
2631    cx_a.foreground().forbid_parking();
2632    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2633    let client_a = server.create_client(cx_a, "user_a").await;
2634    let client_b = server.create_client(cx_b, "user_b").await;
2635    server
2636        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2637        .await;
2638
2639    // Set up a fake language server.
2640    let mut language = Language::new(
2641        LanguageConfig {
2642            name: "Rust".into(),
2643            path_suffixes: vec!["rs".to_string()],
2644            ..Default::default()
2645        },
2646        Some(tree_sitter_rust::language()),
2647    );
2648    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2649    client_a.language_registry.add(Arc::new(language));
2650
2651    client_a
2652        .fs
2653        .insert_tree(
2654            "/root",
2655            json!({
2656                "a.rs": "const ONE: usize = b::TWO;",
2657                "b.rs": "const TWO: usize = 2",
2658            }),
2659        )
2660        .await;
2661    let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
2662    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2663
2664    let buffer_b1 = cx_b
2665        .background()
2666        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2667        .await
2668        .unwrap();
2669
2670    let fake_language_server = fake_language_servers.next().await.unwrap();
2671    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2672        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2673            lsp::Location::new(
2674                lsp::Url::from_file_path("/root/b.rs").unwrap(),
2675                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2676            ),
2677        )))
2678    });
2679
2680    let definitions;
2681    let buffer_b2;
2682    if rng.gen() {
2683        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2684        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2685    } else {
2686        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
2687        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
2688    }
2689
2690    let buffer_b2 = buffer_b2.await.unwrap();
2691    let definitions = definitions.await.unwrap();
2692    assert_eq!(definitions.len(), 1);
2693    assert_eq!(definitions[0].target.buffer, buffer_b2);
2694}
2695
2696#[gpui::test(iterations = 10)]
2697async fn test_collaborating_with_code_actions(
2698    cx_a: &mut TestAppContext,
2699    cx_b: &mut TestAppContext,
2700) {
2701    cx_a.foreground().forbid_parking();
2702    cx_b.update(editor::init);
2703    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2704    let client_a = server.create_client(cx_a, "user_a").await;
2705    let client_b = server.create_client(cx_b, "user_b").await;
2706    server
2707        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2708        .await;
2709
2710    // Set up a fake language server.
2711    let mut language = Language::new(
2712        LanguageConfig {
2713            name: "Rust".into(),
2714            path_suffixes: vec!["rs".to_string()],
2715            ..Default::default()
2716        },
2717        Some(tree_sitter_rust::language()),
2718    );
2719    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2720    client_a.language_registry.add(Arc::new(language));
2721
2722    client_a
2723        .fs
2724        .insert_tree(
2725            "/a",
2726            json!({
2727                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
2728                "other.rs": "pub fn foo() -> usize { 4 }",
2729            }),
2730        )
2731        .await;
2732    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2733
2734    // Join the project as client B.
2735    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2736    let (_window_b, workspace_b) =
2737        cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
2738    let editor_b = workspace_b
2739        .update(cx_b, |workspace, cx| {
2740            workspace.open_path((worktree_id, "main.rs"), true, cx)
2741        })
2742        .await
2743        .unwrap()
2744        .downcast::<Editor>()
2745        .unwrap();
2746
2747    let mut fake_language_server = fake_language_servers.next().await.unwrap();
2748    fake_language_server
2749        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2750            assert_eq!(
2751                params.text_document.uri,
2752                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2753            );
2754            assert_eq!(params.range.start, lsp::Position::new(0, 0));
2755            assert_eq!(params.range.end, lsp::Position::new(0, 0));
2756            Ok(None)
2757        })
2758        .next()
2759        .await;
2760
2761    // Move cursor to a location that contains code actions.
2762    editor_b.update(cx_b, |editor, cx| {
2763        editor.change_selections(None, cx, |s| {
2764            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
2765        });
2766        cx.focus(&editor_b);
2767    });
2768
2769    fake_language_server
2770        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
2771            assert_eq!(
2772                params.text_document.uri,
2773                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2774            );
2775            assert_eq!(params.range.start, lsp::Position::new(1, 31));
2776            assert_eq!(params.range.end, lsp::Position::new(1, 31));
2777
2778            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
2779                lsp::CodeAction {
2780                    title: "Inline into all callers".to_string(),
2781                    edit: Some(lsp::WorkspaceEdit {
2782                        changes: Some(
2783                            [
2784                                (
2785                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
2786                                    vec![lsp::TextEdit::new(
2787                                        lsp::Range::new(
2788                                            lsp::Position::new(1, 22),
2789                                            lsp::Position::new(1, 34),
2790                                        ),
2791                                        "4".to_string(),
2792                                    )],
2793                                ),
2794                                (
2795                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
2796                                    vec![lsp::TextEdit::new(
2797                                        lsp::Range::new(
2798                                            lsp::Position::new(0, 0),
2799                                            lsp::Position::new(0, 27),
2800                                        ),
2801                                        "".to_string(),
2802                                    )],
2803                                ),
2804                            ]
2805                            .into_iter()
2806                            .collect(),
2807                        ),
2808                        ..Default::default()
2809                    }),
2810                    data: Some(json!({
2811                        "codeActionParams": {
2812                            "range": {
2813                                "start": {"line": 1, "column": 31},
2814                                "end": {"line": 1, "column": 31},
2815                            }
2816                        }
2817                    })),
2818                    ..Default::default()
2819                },
2820            )]))
2821        })
2822        .next()
2823        .await;
2824
2825    // Toggle code actions and wait for them to display.
2826    editor_b.update(cx_b, |editor, cx| {
2827        editor.toggle_code_actions(
2828            &ToggleCodeActions {
2829                deployed_from_indicator: false,
2830            },
2831            cx,
2832        );
2833    });
2834    editor_b
2835        .condition(cx_b, |editor, _| editor.context_menu_visible())
2836        .await;
2837
2838    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
2839
2840    // Confirming the code action will trigger a resolve request.
2841    let confirm_action = workspace_b
2842        .update(cx_b, |workspace, cx| {
2843            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
2844        })
2845        .unwrap();
2846    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
2847        |_, _| async move {
2848            Ok(lsp::CodeAction {
2849                title: "Inline into all callers".to_string(),
2850                edit: Some(lsp::WorkspaceEdit {
2851                    changes: Some(
2852                        [
2853                            (
2854                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2855                                vec![lsp::TextEdit::new(
2856                                    lsp::Range::new(
2857                                        lsp::Position::new(1, 22),
2858                                        lsp::Position::new(1, 34),
2859                                    ),
2860                                    "4".to_string(),
2861                                )],
2862                            ),
2863                            (
2864                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
2865                                vec![lsp::TextEdit::new(
2866                                    lsp::Range::new(
2867                                        lsp::Position::new(0, 0),
2868                                        lsp::Position::new(0, 27),
2869                                    ),
2870                                    "".to_string(),
2871                                )],
2872                            ),
2873                        ]
2874                        .into_iter()
2875                        .collect(),
2876                    ),
2877                    ..Default::default()
2878                }),
2879                ..Default::default()
2880            })
2881        },
2882    );
2883
2884    // After the action is confirmed, an editor containing both modified files is opened.
2885    confirm_action.await.unwrap();
2886    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
2887        workspace
2888            .active_item(cx)
2889            .unwrap()
2890            .downcast::<Editor>()
2891            .unwrap()
2892    });
2893    code_action_editor.update(cx_b, |editor, cx| {
2894        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2895        editor.undo(&Undo, cx);
2896        assert_eq!(
2897            editor.text(cx),
2898            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
2899        );
2900        editor.redo(&Redo, cx);
2901        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
2902    });
2903}
2904
2905#[gpui::test(iterations = 10)]
2906async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2907    cx_a.foreground().forbid_parking();
2908    cx_b.update(editor::init);
2909    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2910    let client_a = server.create_client(cx_a, "user_a").await;
2911    let client_b = server.create_client(cx_b, "user_b").await;
2912    server
2913        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
2914        .await;
2915
2916    // Set up a fake language server.
2917    let mut language = Language::new(
2918        LanguageConfig {
2919            name: "Rust".into(),
2920            path_suffixes: vec!["rs".to_string()],
2921            ..Default::default()
2922        },
2923        Some(tree_sitter_rust::language()),
2924    );
2925    let mut fake_language_servers = language
2926        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2927            capabilities: lsp::ServerCapabilities {
2928                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
2929                    prepare_provider: Some(true),
2930                    work_done_progress_options: Default::default(),
2931                })),
2932                ..Default::default()
2933            },
2934            ..Default::default()
2935        }))
2936        .await;
2937    client_a.language_registry.add(Arc::new(language));
2938
2939    client_a
2940        .fs
2941        .insert_tree(
2942            "/dir",
2943            json!({
2944                "one.rs": "const ONE: usize = 1;",
2945                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
2946            }),
2947        )
2948        .await;
2949    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2950    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
2951
2952    let (_window_b, workspace_b) =
2953        cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
2954    let editor_b = workspace_b
2955        .update(cx_b, |workspace, cx| {
2956            workspace.open_path((worktree_id, "one.rs"), true, cx)
2957        })
2958        .await
2959        .unwrap()
2960        .downcast::<Editor>()
2961        .unwrap();
2962    let fake_language_server = fake_language_servers.next().await.unwrap();
2963
2964    // Move cursor to a location that can be renamed.
2965    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
2966        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
2967        editor.rename(&Rename, cx).unwrap()
2968    });
2969
2970    fake_language_server
2971        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
2972            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
2973            assert_eq!(params.position, lsp::Position::new(0, 7));
2974            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
2975                lsp::Position::new(0, 6),
2976                lsp::Position::new(0, 9),
2977            ))))
2978        })
2979        .next()
2980        .await
2981        .unwrap();
2982    prepare_rename.await.unwrap();
2983    editor_b.update(cx_b, |editor, cx| {
2984        let rename = editor.pending_rename().unwrap();
2985        let buffer = editor.buffer().read(cx).snapshot(cx);
2986        assert_eq!(
2987            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
2988            6..9
2989        );
2990        rename.editor.update(cx, |rename_editor, cx| {
2991            rename_editor.buffer().update(cx, |rename_buffer, cx| {
2992                rename_buffer.edit([(0..3, "THREE")], None, cx);
2993            });
2994        });
2995    });
2996
2997    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
2998        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
2999    });
3000    fake_language_server
3001        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
3002            assert_eq!(
3003                params.text_document_position.text_document.uri.as_str(),
3004                "file:///dir/one.rs"
3005            );
3006            assert_eq!(
3007                params.text_document_position.position,
3008                lsp::Position::new(0, 6)
3009            );
3010            assert_eq!(params.new_name, "THREE");
3011            Ok(Some(lsp::WorkspaceEdit {
3012                changes: Some(
3013                    [
3014                        (
3015                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
3016                            vec![lsp::TextEdit::new(
3017                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3018                                "THREE".to_string(),
3019                            )],
3020                        ),
3021                        (
3022                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
3023                            vec![
3024                                lsp::TextEdit::new(
3025                                    lsp::Range::new(
3026                                        lsp::Position::new(0, 24),
3027                                        lsp::Position::new(0, 27),
3028                                    ),
3029                                    "THREE".to_string(),
3030                                ),
3031                                lsp::TextEdit::new(
3032                                    lsp::Range::new(
3033                                        lsp::Position::new(0, 35),
3034                                        lsp::Position::new(0, 38),
3035                                    ),
3036                                    "THREE".to_string(),
3037                                ),
3038                            ],
3039                        ),
3040                    ]
3041                    .into_iter()
3042                    .collect(),
3043                ),
3044                ..Default::default()
3045            }))
3046        })
3047        .next()
3048        .await
3049        .unwrap();
3050    confirm_rename.await.unwrap();
3051
3052    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
3053        workspace
3054            .active_item(cx)
3055            .unwrap()
3056            .downcast::<Editor>()
3057            .unwrap()
3058    });
3059    rename_editor.update(cx_b, |editor, cx| {
3060        assert_eq!(
3061            editor.text(cx),
3062            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3063        );
3064        editor.undo(&Undo, cx);
3065        assert_eq!(
3066            editor.text(cx),
3067            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
3068        );
3069        editor.redo(&Redo, cx);
3070        assert_eq!(
3071            editor.text(cx),
3072            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3073        );
3074    });
3075
3076    // Ensure temporary rename edits cannot be undone/redone.
3077    editor_b.update(cx_b, |editor, cx| {
3078        editor.undo(&Undo, cx);
3079        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3080        editor.undo(&Undo, cx);
3081        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3082        editor.redo(&Redo, cx);
3083        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
3084    })
3085}
3086
3087#[gpui::test(iterations = 10)]
3088async fn test_language_server_statuses(
3089    deterministic: Arc<Deterministic>,
3090    cx_a: &mut TestAppContext,
3091    cx_b: &mut TestAppContext,
3092) {
3093    deterministic.forbid_parking();
3094
3095    cx_b.update(editor::init);
3096    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3097    let client_a = server.create_client(cx_a, "user_a").await;
3098    let client_b = server.create_client(cx_b, "user_b").await;
3099    server
3100        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3101        .await;
3102
3103    // Set up a fake language server.
3104    let mut language = Language::new(
3105        LanguageConfig {
3106            name: "Rust".into(),
3107            path_suffixes: vec!["rs".to_string()],
3108            ..Default::default()
3109        },
3110        Some(tree_sitter_rust::language()),
3111    );
3112    let mut fake_language_servers = language
3113        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3114            name: "the-language-server",
3115            ..Default::default()
3116        }))
3117        .await;
3118    client_a.language_registry.add(Arc::new(language));
3119
3120    client_a
3121        .fs
3122        .insert_tree(
3123            "/dir",
3124            json!({
3125                "main.rs": "const ONE: usize = 1;",
3126            }),
3127        )
3128        .await;
3129    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3130
3131    let _buffer_a = project_a
3132        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3133        .await
3134        .unwrap();
3135
3136    let fake_language_server = fake_language_servers.next().await.unwrap();
3137    fake_language_server.start_progress("the-token").await;
3138    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3139        token: lsp::NumberOrString::String("the-token".to_string()),
3140        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3141            lsp::WorkDoneProgressReport {
3142                message: Some("the-message".to_string()),
3143                ..Default::default()
3144            },
3145        )),
3146    });
3147    deterministic.run_until_parked();
3148    project_a.read_with(cx_a, |project, _| {
3149        let status = project.language_server_statuses().next().unwrap();
3150        assert_eq!(status.name, "the-language-server");
3151        assert_eq!(status.pending_work.len(), 1);
3152        assert_eq!(
3153            status.pending_work["the-token"].message.as_ref().unwrap(),
3154            "the-message"
3155        );
3156    });
3157
3158    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3159    project_b.read_with(cx_b, |project, _| {
3160        let status = project.language_server_statuses().next().unwrap();
3161        assert_eq!(status.name, "the-language-server");
3162    });
3163
3164    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3165        token: lsp::NumberOrString::String("the-token".to_string()),
3166        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3167            lsp::WorkDoneProgressReport {
3168                message: Some("the-message-2".to_string()),
3169                ..Default::default()
3170            },
3171        )),
3172    });
3173    deterministic.run_until_parked();
3174    project_a.read_with(cx_a, |project, _| {
3175        let status = project.language_server_statuses().next().unwrap();
3176        assert_eq!(status.name, "the-language-server");
3177        assert_eq!(status.pending_work.len(), 1);
3178        assert_eq!(
3179            status.pending_work["the-token"].message.as_ref().unwrap(),
3180            "the-message-2"
3181        );
3182    });
3183    project_b.read_with(cx_b, |project, _| {
3184        let status = project.language_server_statuses().next().unwrap();
3185        assert_eq!(status.name, "the-language-server");
3186        assert_eq!(status.pending_work.len(), 1);
3187        assert_eq!(
3188            status.pending_work["the-token"].message.as_ref().unwrap(),
3189            "the-message-2"
3190        );
3191    });
3192}
3193
3194#[gpui::test(iterations = 10)]
3195async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3196    cx_a.foreground().forbid_parking();
3197    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3198    let client_a = server.create_client(cx_a, "user_a").await;
3199    let client_b = server.create_client(cx_b, "user_b").await;
3200
3201    // Create an org that includes these 2 users.
3202    let db = &server.app_state.db;
3203    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3204    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
3205        .await
3206        .unwrap();
3207    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
3208        .await
3209        .unwrap();
3210
3211    // Create a channel that includes all the users.
3212    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3213    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
3214        .await
3215        .unwrap();
3216    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
3217        .await
3218        .unwrap();
3219    db.create_channel_message(
3220        channel_id,
3221        client_b.current_user_id(cx_b),
3222        "hello A, it's B.",
3223        OffsetDateTime::now_utc(),
3224        1,
3225    )
3226    .await
3227    .unwrap();
3228
3229    let channels_a =
3230        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3231    channels_a
3232        .condition(cx_a, |list, _| list.available_channels().is_some())
3233        .await;
3234    channels_a.read_with(cx_a, |list, _| {
3235        assert_eq!(
3236            list.available_channels().unwrap(),
3237            &[ChannelDetails {
3238                id: channel_id.to_proto(),
3239                name: "test-channel".to_string()
3240            }]
3241        )
3242    });
3243    let channel_a = channels_a.update(cx_a, |this, cx| {
3244        this.get_channel(channel_id.to_proto(), cx).unwrap()
3245    });
3246    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3247    channel_a
3248        .condition(cx_a, |channel, _| {
3249            channel_messages(channel)
3250                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3251        })
3252        .await;
3253
3254    let channels_b =
3255        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3256    channels_b
3257        .condition(cx_b, |list, _| list.available_channels().is_some())
3258        .await;
3259    channels_b.read_with(cx_b, |list, _| {
3260        assert_eq!(
3261            list.available_channels().unwrap(),
3262            &[ChannelDetails {
3263                id: channel_id.to_proto(),
3264                name: "test-channel".to_string()
3265            }]
3266        )
3267    });
3268
3269    let channel_b = channels_b.update(cx_b, |this, cx| {
3270        this.get_channel(channel_id.to_proto(), cx).unwrap()
3271    });
3272    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3273    channel_b
3274        .condition(cx_b, |channel, _| {
3275            channel_messages(channel)
3276                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3277        })
3278        .await;
3279
3280    channel_a
3281        .update(cx_a, |channel, cx| {
3282            channel
3283                .send_message("oh, hi B.".to_string(), cx)
3284                .unwrap()
3285                .detach();
3286            let task = channel.send_message("sup".to_string(), cx).unwrap();
3287            assert_eq!(
3288                channel_messages(channel),
3289                &[
3290                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3291                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3292                    ("user_a".to_string(), "sup".to_string(), true)
3293                ]
3294            );
3295            task
3296        })
3297        .await
3298        .unwrap();
3299
3300    channel_b
3301        .condition(cx_b, |channel, _| {
3302            channel_messages(channel)
3303                == [
3304                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3305                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3306                    ("user_a".to_string(), "sup".to_string(), false),
3307                ]
3308        })
3309        .await;
3310
3311    assert_eq!(
3312        server
3313            .store()
3314            .await
3315            .channel(channel_id)
3316            .unwrap()
3317            .connection_ids
3318            .len(),
3319        2
3320    );
3321    cx_b.update(|_| drop(channel_b));
3322    server
3323        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
3324        .await;
3325
3326    cx_a.update(|_| drop(channel_a));
3327    server
3328        .condition(|state| state.channel(channel_id).is_none())
3329        .await;
3330}
3331
3332#[gpui::test(iterations = 10)]
3333async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
3334    cx_a.foreground().forbid_parking();
3335    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3336    let client_a = server.create_client(cx_a, "user_a").await;
3337
3338    let db = &server.app_state.db;
3339    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3340    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3341    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
3342        .await
3343        .unwrap();
3344    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
3345        .await
3346        .unwrap();
3347
3348    let channels_a =
3349        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3350    channels_a
3351        .condition(cx_a, |list, _| list.available_channels().is_some())
3352        .await;
3353    let channel_a = channels_a.update(cx_a, |this, cx| {
3354        this.get_channel(channel_id.to_proto(), cx).unwrap()
3355    });
3356
3357    // Messages aren't allowed to be too long.
3358    channel_a
3359        .update(cx_a, |channel, cx| {
3360            let long_body = "this is long.\n".repeat(1024);
3361            channel.send_message(long_body, cx).unwrap()
3362        })
3363        .await
3364        .unwrap_err();
3365
3366    // Messages aren't allowed to be blank.
3367    channel_a.update(cx_a, |channel, cx| {
3368        channel.send_message(String::new(), cx).unwrap_err()
3369    });
3370
3371    // Leading and trailing whitespace are trimmed.
3372    channel_a
3373        .update(cx_a, |channel, cx| {
3374            channel
3375                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
3376                .unwrap()
3377        })
3378        .await
3379        .unwrap();
3380    assert_eq!(
3381        db.get_channel_messages(channel_id, 10, None)
3382            .await
3383            .unwrap()
3384            .iter()
3385            .map(|m| &m.body)
3386            .collect::<Vec<_>>(),
3387        &["surrounded by whitespace"]
3388    );
3389}
3390
3391#[gpui::test(iterations = 10)]
3392async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3393    cx_a.foreground().forbid_parking();
3394    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3395    let client_a = server.create_client(cx_a, "user_a").await;
3396    let client_b = server.create_client(cx_b, "user_b").await;
3397
3398    let mut status_b = client_b.status();
3399
3400    // Create an org that includes these 2 users.
3401    let db = &server.app_state.db;
3402    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3403    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
3404        .await
3405        .unwrap();
3406    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
3407        .await
3408        .unwrap();
3409
3410    // Create a channel that includes all the users.
3411    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3412    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
3413        .await
3414        .unwrap();
3415    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
3416        .await
3417        .unwrap();
3418    db.create_channel_message(
3419        channel_id,
3420        client_b.current_user_id(cx_b),
3421        "hello A, it's B.",
3422        OffsetDateTime::now_utc(),
3423        2,
3424    )
3425    .await
3426    .unwrap();
3427
3428    let channels_a =
3429        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3430    channels_a
3431        .condition(cx_a, |list, _| list.available_channels().is_some())
3432        .await;
3433
3434    channels_a.read_with(cx_a, |list, _| {
3435        assert_eq!(
3436            list.available_channels().unwrap(),
3437            &[ChannelDetails {
3438                id: channel_id.to_proto(),
3439                name: "test-channel".to_string()
3440            }]
3441        )
3442    });
3443    let channel_a = channels_a.update(cx_a, |this, cx| {
3444        this.get_channel(channel_id.to_proto(), cx).unwrap()
3445    });
3446    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3447    channel_a
3448        .condition(cx_a, |channel, _| {
3449            channel_messages(channel)
3450                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3451        })
3452        .await;
3453
3454    let channels_b =
3455        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3456    channels_b
3457        .condition(cx_b, |list, _| list.available_channels().is_some())
3458        .await;
3459    channels_b.read_with(cx_b, |list, _| {
3460        assert_eq!(
3461            list.available_channels().unwrap(),
3462            &[ChannelDetails {
3463                id: channel_id.to_proto(),
3464                name: "test-channel".to_string()
3465            }]
3466        )
3467    });
3468
3469    let channel_b = channels_b.update(cx_b, |this, cx| {
3470        this.get_channel(channel_id.to_proto(), cx).unwrap()
3471    });
3472    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3473    channel_b
3474        .condition(cx_b, |channel, _| {
3475            channel_messages(channel)
3476                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3477        })
3478        .await;
3479
3480    // Disconnect client B, ensuring we can still access its cached channel data.
3481    server.forbid_connections();
3482    server.disconnect_client(client_b.current_user_id(cx_b));
3483    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
3484    while !matches!(
3485        status_b.next().await,
3486        Some(client::Status::ReconnectionError { .. })
3487    ) {}
3488
3489    channels_b.read_with(cx_b, |channels, _| {
3490        assert_eq!(
3491            channels.available_channels().unwrap(),
3492            [ChannelDetails {
3493                id: channel_id.to_proto(),
3494                name: "test-channel".to_string()
3495            }]
3496        )
3497    });
3498    channel_b.read_with(cx_b, |channel, _| {
3499        assert_eq!(
3500            channel_messages(channel),
3501            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3502        )
3503    });
3504
3505    // Send a message from client B while it is disconnected.
3506    channel_b
3507        .update(cx_b, |channel, cx| {
3508            let task = channel
3509                .send_message("can you see this?".to_string(), cx)
3510                .unwrap();
3511            assert_eq!(
3512                channel_messages(channel),
3513                &[
3514                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3515                    ("user_b".to_string(), "can you see this?".to_string(), true)
3516                ]
3517            );
3518            task
3519        })
3520        .await
3521        .unwrap_err();
3522
3523    // Send a message from client A while B is disconnected.
3524    channel_a
3525        .update(cx_a, |channel, cx| {
3526            channel
3527                .send_message("oh, hi B.".to_string(), cx)
3528                .unwrap()
3529                .detach();
3530            let task = channel.send_message("sup".to_string(), cx).unwrap();
3531            assert_eq!(
3532                channel_messages(channel),
3533                &[
3534                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3535                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
3536                    ("user_a".to_string(), "sup".to_string(), true)
3537                ]
3538            );
3539            task
3540        })
3541        .await
3542        .unwrap();
3543
3544    // Give client B a chance to reconnect.
3545    server.allow_connections();
3546    cx_b.foreground().advance_clock(Duration::from_secs(10));
3547
3548    // Verify that B sees the new messages upon reconnection, as well as the message client B
3549    // sent while offline.
3550    channel_b
3551        .condition(cx_b, |channel, _| {
3552            channel_messages(channel)
3553                == [
3554                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3555                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3556                    ("user_a".to_string(), "sup".to_string(), false),
3557                    ("user_b".to_string(), "can you see this?".to_string(), false),
3558                ]
3559        })
3560        .await;
3561
3562    // Ensure client A and B can communicate normally after reconnection.
3563    channel_a
3564        .update(cx_a, |channel, cx| {
3565            channel.send_message("you online?".to_string(), cx).unwrap()
3566        })
3567        .await
3568        .unwrap();
3569    channel_b
3570        .condition(cx_b, |channel, _| {
3571            channel_messages(channel)
3572                == [
3573                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3574                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3575                    ("user_a".to_string(), "sup".to_string(), false),
3576                    ("user_b".to_string(), "can you see this?".to_string(), false),
3577                    ("user_a".to_string(), "you online?".to_string(), false),
3578                ]
3579        })
3580        .await;
3581
3582    channel_b
3583        .update(cx_b, |channel, cx| {
3584            channel.send_message("yep".to_string(), cx).unwrap()
3585        })
3586        .await
3587        .unwrap();
3588    channel_a
3589        .condition(cx_a, |channel, _| {
3590            channel_messages(channel)
3591                == [
3592                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
3593                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
3594                    ("user_a".to_string(), "sup".to_string(), false),
3595                    ("user_b".to_string(), "can you see this?".to_string(), false),
3596                    ("user_a".to_string(), "you online?".to_string(), false),
3597                    ("user_b".to_string(), "yep".to_string(), false),
3598                ]
3599        })
3600        .await;
3601}
3602
3603#[gpui::test(iterations = 10)]
3604async fn test_contacts(
3605    deterministic: Arc<Deterministic>,
3606    cx_a: &mut TestAppContext,
3607    cx_b: &mut TestAppContext,
3608    cx_c: &mut TestAppContext,
3609) {
3610    cx_a.foreground().forbid_parking();
3611    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3612    let client_a = server.create_client(cx_a, "user_a").await;
3613    let client_b = server.create_client(cx_b, "user_b").await;
3614    let client_c = server.create_client(cx_c, "user_c").await;
3615    server
3616        .make_contacts(vec![
3617            (&client_a, cx_a),
3618            (&client_b, cx_b),
3619            (&client_c, cx_c),
3620        ])
3621        .await;
3622
3623    deterministic.run_until_parked();
3624    assert_eq!(
3625        contacts(&client_a, cx_a),
3626        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3627    );
3628    assert_eq!(
3629        contacts(&client_b, cx_b),
3630        [("user_a".to_string(), true), ("user_c".to_string(), true)]
3631    );
3632    assert_eq!(
3633        contacts(&client_c, cx_c),
3634        [("user_a".to_string(), true), ("user_b".to_string(), true)]
3635    );
3636
3637    // Share a project as client A.
3638    client_a.fs.create_dir(Path::new("/a")).await.unwrap();
3639    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3640
3641    deterministic.run_until_parked();
3642    assert_eq!(
3643        contacts(&client_a, cx_a),
3644        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3645    );
3646    assert_eq!(
3647        contacts(&client_b, cx_b),
3648        [("user_a".to_string(), true), ("user_c".to_string(), true)]
3649    );
3650    assert_eq!(
3651        contacts(&client_c, cx_c),
3652        [("user_a".to_string(), true), ("user_b".to_string(), true)]
3653    );
3654
3655    let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3656
3657    deterministic.run_until_parked();
3658    assert_eq!(
3659        contacts(&client_a, cx_a),
3660        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3661    );
3662    assert_eq!(
3663        contacts(&client_b, cx_b),
3664        [("user_a".to_string(), true,), ("user_c".to_string(), true)]
3665    );
3666    assert_eq!(
3667        contacts(&client_c, cx_c),
3668        [("user_a".to_string(), true,), ("user_b".to_string(), true)]
3669    );
3670
3671    // Add a local project as client B
3672    client_a.fs.create_dir("/b".as_ref()).await.unwrap();
3673    let (_project_b, _) = client_b.build_local_project("/b", cx_b).await;
3674
3675    deterministic.run_until_parked();
3676    assert_eq!(
3677        contacts(&client_a, cx_a),
3678        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3679    );
3680    assert_eq!(
3681        contacts(&client_b, cx_b),
3682        [("user_a".to_string(), true), ("user_c".to_string(), true)]
3683    );
3684    assert_eq!(
3685        contacts(&client_c, cx_c),
3686        [("user_a".to_string(), true,), ("user_b".to_string(), true)]
3687    );
3688
3689    project_a
3690        .condition(cx_a, |project, _| {
3691            project.collaborators().contains_key(&client_b.peer_id)
3692        })
3693        .await;
3694
3695    cx_a.update(move |_| drop(project_a));
3696    deterministic.run_until_parked();
3697    assert_eq!(
3698        contacts(&client_a, cx_a),
3699        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3700    );
3701    assert_eq!(
3702        contacts(&client_b, cx_b),
3703        [("user_a".to_string(), true), ("user_c".to_string(), true)]
3704    );
3705    assert_eq!(
3706        contacts(&client_c, cx_c),
3707        [("user_a".to_string(), true), ("user_b".to_string(), true)]
3708    );
3709
3710    server.disconnect_client(client_c.current_user_id(cx_c));
3711    server.forbid_connections();
3712    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
3713    assert_eq!(
3714        contacts(&client_a, cx_a),
3715        [("user_b".to_string(), true), ("user_c".to_string(), false)]
3716    );
3717    assert_eq!(
3718        contacts(&client_b, cx_b),
3719        [("user_a".to_string(), true), ("user_c".to_string(), false)]
3720    );
3721    assert_eq!(contacts(&client_c, cx_c), []);
3722
3723    server.allow_connections();
3724    client_c
3725        .authenticate_and_connect(false, &cx_c.to_async())
3726        .await
3727        .unwrap();
3728
3729    deterministic.run_until_parked();
3730    assert_eq!(
3731        contacts(&client_a, cx_a),
3732        [("user_b".to_string(), true), ("user_c".to_string(), true)]
3733    );
3734    assert_eq!(
3735        contacts(&client_b, cx_b),
3736        [("user_a".to_string(), true), ("user_c".to_string(), true)]
3737    );
3738    assert_eq!(
3739        contacts(&client_c, cx_c),
3740        [("user_a".to_string(), true), ("user_b".to_string(), true)]
3741    );
3742
3743    #[allow(clippy::type_complexity)]
3744    fn contacts(client: &TestClient, cx: &TestAppContext) -> Vec<(String, bool)> {
3745        client.user_store.read_with(cx, |store, _| {
3746            store
3747                .contacts()
3748                .iter()
3749                .map(|contact| (contact.user.github_login.clone(), contact.online))
3750                .collect()
3751        })
3752    }
3753}
3754
3755#[gpui::test(iterations = 10)]
3756async fn test_contact_requests(
3757    executor: Arc<Deterministic>,
3758    cx_a: &mut TestAppContext,
3759    cx_a2: &mut TestAppContext,
3760    cx_b: &mut TestAppContext,
3761    cx_b2: &mut TestAppContext,
3762    cx_c: &mut TestAppContext,
3763    cx_c2: &mut TestAppContext,
3764) {
3765    cx_a.foreground().forbid_parking();
3766
3767    // Connect to a server as 3 clients.
3768    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3769    let client_a = server.create_client(cx_a, "user_a").await;
3770    let client_a2 = server.create_client(cx_a2, "user_a").await;
3771    let client_b = server.create_client(cx_b, "user_b").await;
3772    let client_b2 = server.create_client(cx_b2, "user_b").await;
3773    let client_c = server.create_client(cx_c, "user_c").await;
3774    let client_c2 = server.create_client(cx_c2, "user_c").await;
3775
3776    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
3777    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
3778    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
3779
3780    // User A and User C request that user B become their contact.
3781    client_a
3782        .user_store
3783        .update(cx_a, |store, cx| {
3784            store.request_contact(client_b.user_id().unwrap(), cx)
3785        })
3786        .await
3787        .unwrap();
3788    client_c
3789        .user_store
3790        .update(cx_c, |store, cx| {
3791            store.request_contact(client_b.user_id().unwrap(), cx)
3792        })
3793        .await
3794        .unwrap();
3795    executor.run_until_parked();
3796
3797    // All users see the pending request appear in all their clients.
3798    assert_eq!(
3799        client_a.summarize_contacts(cx_a).outgoing_requests,
3800        &["user_b"]
3801    );
3802    assert_eq!(
3803        client_a2.summarize_contacts(cx_a2).outgoing_requests,
3804        &["user_b"]
3805    );
3806    assert_eq!(
3807        client_b.summarize_contacts(cx_b).incoming_requests,
3808        &["user_a", "user_c"]
3809    );
3810    assert_eq!(
3811        client_b2.summarize_contacts(cx_b2).incoming_requests,
3812        &["user_a", "user_c"]
3813    );
3814    assert_eq!(
3815        client_c.summarize_contacts(cx_c).outgoing_requests,
3816        &["user_b"]
3817    );
3818    assert_eq!(
3819        client_c2.summarize_contacts(cx_c2).outgoing_requests,
3820        &["user_b"]
3821    );
3822
3823    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
3824    disconnect_and_reconnect(&client_a, cx_a).await;
3825    disconnect_and_reconnect(&client_b, cx_b).await;
3826    disconnect_and_reconnect(&client_c, cx_c).await;
3827    executor.run_until_parked();
3828    assert_eq!(
3829        client_a.summarize_contacts(cx_a).outgoing_requests,
3830        &["user_b"]
3831    );
3832    assert_eq!(
3833        client_b.summarize_contacts(cx_b).incoming_requests,
3834        &["user_a", "user_c"]
3835    );
3836    assert_eq!(
3837        client_c.summarize_contacts(cx_c).outgoing_requests,
3838        &["user_b"]
3839    );
3840
3841    // User B accepts the request from user A.
3842    client_b
3843        .user_store
3844        .update(cx_b, |store, cx| {
3845            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
3846        })
3847        .await
3848        .unwrap();
3849
3850    executor.run_until_parked();
3851
3852    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
3853    let contacts_b = client_b.summarize_contacts(cx_b);
3854    assert_eq!(contacts_b.current, &["user_a"]);
3855    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
3856    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
3857    assert_eq!(contacts_b2.current, &["user_a"]);
3858    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
3859
3860    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
3861    let contacts_a = client_a.summarize_contacts(cx_a);
3862    assert_eq!(contacts_a.current, &["user_b"]);
3863    assert!(contacts_a.outgoing_requests.is_empty());
3864    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
3865    assert_eq!(contacts_a2.current, &["user_b"]);
3866    assert!(contacts_a2.outgoing_requests.is_empty());
3867
3868    // Contacts are present upon connecting (tested here via disconnect/reconnect)
3869    disconnect_and_reconnect(&client_a, cx_a).await;
3870    disconnect_and_reconnect(&client_b, cx_b).await;
3871    disconnect_and_reconnect(&client_c, cx_c).await;
3872    executor.run_until_parked();
3873    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
3874    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
3875    assert_eq!(
3876        client_b.summarize_contacts(cx_b).incoming_requests,
3877        &["user_c"]
3878    );
3879    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
3880    assert_eq!(
3881        client_c.summarize_contacts(cx_c).outgoing_requests,
3882        &["user_b"]
3883    );
3884
3885    // User B rejects the request from user C.
3886    client_b
3887        .user_store
3888        .update(cx_b, |store, cx| {
3889            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
3890        })
3891        .await
3892        .unwrap();
3893
3894    executor.run_until_parked();
3895
3896    // User B doesn't see user C as their contact, and the incoming request from them is removed.
3897    let contacts_b = client_b.summarize_contacts(cx_b);
3898    assert_eq!(contacts_b.current, &["user_a"]);
3899    assert!(contacts_b.incoming_requests.is_empty());
3900    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
3901    assert_eq!(contacts_b2.current, &["user_a"]);
3902    assert!(contacts_b2.incoming_requests.is_empty());
3903
3904    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
3905    let contacts_c = client_c.summarize_contacts(cx_c);
3906    assert!(contacts_c.current.is_empty());
3907    assert!(contacts_c.outgoing_requests.is_empty());
3908    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
3909    assert!(contacts_c2.current.is_empty());
3910    assert!(contacts_c2.outgoing_requests.is_empty());
3911
3912    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
3913    disconnect_and_reconnect(&client_a, cx_a).await;
3914    disconnect_and_reconnect(&client_b, cx_b).await;
3915    disconnect_and_reconnect(&client_c, cx_c).await;
3916    executor.run_until_parked();
3917    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
3918    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
3919    assert!(client_b
3920        .summarize_contacts(cx_b)
3921        .incoming_requests
3922        .is_empty());
3923    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
3924    assert!(client_c
3925        .summarize_contacts(cx_c)
3926        .outgoing_requests
3927        .is_empty());
3928
3929    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
3930        client.disconnect(&cx.to_async()).unwrap();
3931        client.clear_contacts(cx).await;
3932        client
3933            .authenticate_and_connect(false, &cx.to_async())
3934            .await
3935            .unwrap();
3936    }
3937}
3938
3939#[gpui::test(iterations = 10)]
3940async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3941    cx_a.foreground().forbid_parking();
3942    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3943    let client_a = server.create_client(cx_a, "user_a").await;
3944    let client_b = server.create_client(cx_b, "user_b").await;
3945    server
3946        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
3947        .await;
3948    cx_a.update(editor::init);
3949    cx_b.update(editor::init);
3950
3951    client_a
3952        .fs
3953        .insert_tree(
3954            "/a",
3955            json!({
3956                "1.txt": "one",
3957                "2.txt": "two",
3958                "3.txt": "three",
3959            }),
3960        )
3961        .await;
3962    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3963
3964    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
3965
3966    // Client A opens some editors.
3967    let workspace_a = client_a.build_workspace(&project_a, cx_a);
3968    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
3969    let editor_a1 = workspace_a
3970        .update(cx_a, |workspace, cx| {
3971            workspace.open_path((worktree_id, "1.txt"), true, cx)
3972        })
3973        .await
3974        .unwrap()
3975        .downcast::<Editor>()
3976        .unwrap();
3977    let editor_a2 = workspace_a
3978        .update(cx_a, |workspace, cx| {
3979            workspace.open_path((worktree_id, "2.txt"), true, cx)
3980        })
3981        .await
3982        .unwrap()
3983        .downcast::<Editor>()
3984        .unwrap();
3985
3986    // Client B opens an editor.
3987    let workspace_b = client_b.build_workspace(&project_b, cx_b);
3988    let editor_b1 = workspace_b
3989        .update(cx_b, |workspace, cx| {
3990            workspace.open_path((worktree_id, "1.txt"), true, cx)
3991        })
3992        .await
3993        .unwrap()
3994        .downcast::<Editor>()
3995        .unwrap();
3996
3997    let client_a_id = project_b.read_with(cx_b, |project, _| {
3998        project.collaborators().values().next().unwrap().peer_id
3999    });
4000    let client_b_id = project_a.read_with(cx_a, |project, _| {
4001        project.collaborators().values().next().unwrap().peer_id
4002    });
4003
4004    // When client B starts following client A, all visible view states are replicated to client B.
4005    editor_a1.update(cx_a, |editor, cx| {
4006        editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
4007    });
4008    editor_a2.update(cx_a, |editor, cx| {
4009        editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
4010    });
4011    workspace_b
4012        .update(cx_b, |workspace, cx| {
4013            workspace
4014                .toggle_follow(&ToggleFollow(client_a_id), cx)
4015                .unwrap()
4016        })
4017        .await
4018        .unwrap();
4019
4020    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4021        workspace
4022            .active_item(cx)
4023            .unwrap()
4024            .downcast::<Editor>()
4025            .unwrap()
4026    });
4027    assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
4028    assert_eq!(
4029        editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
4030        Some((worktree_id, "2.txt").into())
4031    );
4032    assert_eq!(
4033        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4034        vec![2..3]
4035    );
4036    assert_eq!(
4037        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4038        vec![0..1]
4039    );
4040
4041    // When client A activates a different editor, client B does so as well.
4042    workspace_a.update(cx_a, |workspace, cx| {
4043        workspace.activate_item(&editor_a1, cx)
4044    });
4045    workspace_b
4046        .condition(cx_b, |workspace, cx| {
4047            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4048        })
4049        .await;
4050
4051    // When client A navigates back and forth, client B does so as well.
4052    workspace_a
4053        .update(cx_a, |workspace, cx| {
4054            workspace::Pane::go_back(workspace, None, cx)
4055        })
4056        .await;
4057    workspace_b
4058        .condition(cx_b, |workspace, cx| {
4059            workspace.active_item(cx).unwrap().id() == editor_b2.id()
4060        })
4061        .await;
4062
4063    workspace_a
4064        .update(cx_a, |workspace, cx| {
4065            workspace::Pane::go_forward(workspace, None, cx)
4066        })
4067        .await;
4068    workspace_b
4069        .condition(cx_b, |workspace, cx| {
4070            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4071        })
4072        .await;
4073
4074    // Changes to client A's editor are reflected on client B.
4075    editor_a1.update(cx_a, |editor, cx| {
4076        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
4077    });
4078    editor_b1
4079        .condition(cx_b, |editor, cx| {
4080            editor.selections.ranges(cx) == vec![1..1, 2..2]
4081        })
4082        .await;
4083
4084    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
4085    editor_b1
4086        .condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
4087        .await;
4088
4089    editor_a1.update(cx_a, |editor, cx| {
4090        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4091        editor.set_scroll_position(vec2f(0., 100.), cx);
4092    });
4093    editor_b1
4094        .condition(cx_b, |editor, cx| {
4095            editor.selections.ranges(cx) == vec![3..3]
4096        })
4097        .await;
4098
4099    // After unfollowing, client B stops receiving updates from client A.
4100    workspace_b.update(cx_b, |workspace, cx| {
4101        workspace.unfollow(&workspace.active_pane().clone(), cx)
4102    });
4103    workspace_a.update(cx_a, |workspace, cx| {
4104        workspace.activate_item(&editor_a2, cx)
4105    });
4106    cx_a.foreground().run_until_parked();
4107    assert_eq!(
4108        workspace_b.read_with(cx_b, |workspace, cx| workspace
4109            .active_item(cx)
4110            .unwrap()
4111            .id()),
4112        editor_b1.id()
4113    );
4114
4115    // Client A starts following client B.
4116    workspace_a
4117        .update(cx_a, |workspace, cx| {
4118            workspace
4119                .toggle_follow(&ToggleFollow(client_b_id), cx)
4120                .unwrap()
4121        })
4122        .await
4123        .unwrap();
4124    assert_eq!(
4125        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4126        Some(client_b_id)
4127    );
4128    assert_eq!(
4129        workspace_a.read_with(cx_a, |workspace, cx| workspace
4130            .active_item(cx)
4131            .unwrap()
4132            .id()),
4133        editor_a1.id()
4134    );
4135
4136    // Following interrupts when client B disconnects.
4137    client_b.disconnect(&cx_b.to_async()).unwrap();
4138    cx_a.foreground().run_until_parked();
4139    assert_eq!(
4140        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
4141        None
4142    );
4143}
4144
4145#[gpui::test(iterations = 10)]
4146async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4147    cx_a.foreground().forbid_parking();
4148    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4149    let client_a = server.create_client(cx_a, "user_a").await;
4150    let client_b = server.create_client(cx_b, "user_b").await;
4151    server
4152        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4153        .await;
4154    cx_a.update(editor::init);
4155    cx_b.update(editor::init);
4156
4157    // Client A shares a project.
4158    client_a
4159        .fs
4160        .insert_tree(
4161            "/a",
4162            json!({
4163                "1.txt": "one",
4164                "2.txt": "two",
4165                "3.txt": "three",
4166                "4.txt": "four",
4167            }),
4168        )
4169        .await;
4170    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4171
4172    // Client B joins the project.
4173    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4174
4175    // Client A opens some editors.
4176    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4177    let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
4178    let _editor_a1 = workspace_a
4179        .update(cx_a, |workspace, cx| {
4180            workspace.open_path((worktree_id, "1.txt"), true, cx)
4181        })
4182        .await
4183        .unwrap()
4184        .downcast::<Editor>()
4185        .unwrap();
4186
4187    // Client B opens an editor.
4188    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4189    let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4190    let _editor_b1 = workspace_b
4191        .update(cx_b, |workspace, cx| {
4192            workspace.open_path((worktree_id, "2.txt"), true, cx)
4193        })
4194        .await
4195        .unwrap()
4196        .downcast::<Editor>()
4197        .unwrap();
4198
4199    // Clients A and B follow each other in split panes
4200    workspace_a.update(cx_a, |workspace, cx| {
4201        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4202        let pane_a1 = pane_a1.clone();
4203        cx.defer(move |workspace, _| {
4204            assert_ne!(*workspace.active_pane(), pane_a1);
4205        });
4206    });
4207    workspace_a
4208        .update(cx_a, |workspace, cx| {
4209            let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
4210            workspace
4211                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4212                .unwrap()
4213        })
4214        .await
4215        .unwrap();
4216    workspace_b.update(cx_b, |workspace, cx| {
4217        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
4218        let pane_b1 = pane_b1.clone();
4219        cx.defer(move |workspace, _| {
4220            assert_ne!(*workspace.active_pane(), pane_b1);
4221        });
4222    });
4223    workspace_b
4224        .update(cx_b, |workspace, cx| {
4225            let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
4226            workspace
4227                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
4228                .unwrap()
4229        })
4230        .await
4231        .unwrap();
4232
4233    workspace_a.update(cx_a, |workspace, cx| {
4234        workspace.activate_next_pane(cx);
4235    });
4236    // Wait for focus effects to be fully flushed
4237    workspace_a.update(cx_a, |workspace, _| {
4238        assert_eq!(*workspace.active_pane(), pane_a1);
4239    });
4240
4241    workspace_a
4242        .update(cx_a, |workspace, cx| {
4243            workspace.open_path((worktree_id, "3.txt"), true, cx)
4244        })
4245        .await
4246        .unwrap();
4247    workspace_b.update(cx_b, |workspace, cx| {
4248        workspace.activate_next_pane(cx);
4249    });
4250
4251    workspace_b
4252        .update(cx_b, |workspace, cx| {
4253            assert_eq!(*workspace.active_pane(), pane_b1);
4254            workspace.open_path((worktree_id, "4.txt"), true, cx)
4255        })
4256        .await
4257        .unwrap();
4258    cx_a.foreground().run_until_parked();
4259
4260    // Ensure leader updates don't change the active pane of followers
4261    workspace_a.read_with(cx_a, |workspace, _| {
4262        assert_eq!(*workspace.active_pane(), pane_a1);
4263    });
4264    workspace_b.read_with(cx_b, |workspace, _| {
4265        assert_eq!(*workspace.active_pane(), pane_b1);
4266    });
4267
4268    // Ensure peers following each other doesn't cause an infinite loop.
4269    assert_eq!(
4270        workspace_a.read_with(cx_a, |workspace, cx| workspace
4271            .active_item(cx)
4272            .unwrap()
4273            .project_path(cx)),
4274        Some((worktree_id, "3.txt").into())
4275    );
4276    workspace_a.update(cx_a, |workspace, cx| {
4277        assert_eq!(
4278            workspace.active_item(cx).unwrap().project_path(cx),
4279            Some((worktree_id, "3.txt").into())
4280        );
4281        workspace.activate_next_pane(cx);
4282    });
4283
4284    workspace_a.update(cx_a, |workspace, cx| {
4285        assert_eq!(
4286            workspace.active_item(cx).unwrap().project_path(cx),
4287            Some((worktree_id, "4.txt").into())
4288        );
4289    });
4290
4291    workspace_b.update(cx_b, |workspace, cx| {
4292        assert_eq!(
4293            workspace.active_item(cx).unwrap().project_path(cx),
4294            Some((worktree_id, "4.txt").into())
4295        );
4296        workspace.activate_next_pane(cx);
4297    });
4298
4299    workspace_b.update(cx_b, |workspace, cx| {
4300        assert_eq!(
4301            workspace.active_item(cx).unwrap().project_path(cx),
4302            Some((worktree_id, "3.txt").into())
4303        );
4304    });
4305}
4306
4307#[gpui::test(iterations = 10)]
4308async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4309    cx_a.foreground().forbid_parking();
4310
4311    // 2 clients connect to a server.
4312    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4313    let client_a = server.create_client(cx_a, "user_a").await;
4314    let client_b = server.create_client(cx_b, "user_b").await;
4315    server
4316        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4317        .await;
4318    cx_a.update(editor::init);
4319    cx_b.update(editor::init);
4320
4321    // Client A shares a project.
4322    client_a
4323        .fs
4324        .insert_tree(
4325            "/a",
4326            json!({
4327                "1.txt": "one",
4328                "2.txt": "two",
4329                "3.txt": "three",
4330            }),
4331        )
4332        .await;
4333    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4334    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4335
4336    // Client A opens some editors.
4337    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4338    let _editor_a1 = workspace_a
4339        .update(cx_a, |workspace, cx| {
4340            workspace.open_path((worktree_id, "1.txt"), true, cx)
4341        })
4342        .await
4343        .unwrap()
4344        .downcast::<Editor>()
4345        .unwrap();
4346
4347    // Client B starts following client A.
4348    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4349    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
4350    let leader_id = project_b.read_with(cx_b, |project, _| {
4351        project.collaborators().values().next().unwrap().peer_id
4352    });
4353    workspace_b
4354        .update(cx_b, |workspace, cx| {
4355            workspace
4356                .toggle_follow(&ToggleFollow(leader_id), cx)
4357                .unwrap()
4358        })
4359        .await
4360        .unwrap();
4361    assert_eq!(
4362        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4363        Some(leader_id)
4364    );
4365    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4366        workspace
4367            .active_item(cx)
4368            .unwrap()
4369            .downcast::<Editor>()
4370            .unwrap()
4371    });
4372
4373    // When client B moves, it automatically stops following client A.
4374    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
4375    assert_eq!(
4376        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4377        None
4378    );
4379
4380    workspace_b
4381        .update(cx_b, |workspace, cx| {
4382            workspace
4383                .toggle_follow(&ToggleFollow(leader_id), cx)
4384                .unwrap()
4385        })
4386        .await
4387        .unwrap();
4388    assert_eq!(
4389        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4390        Some(leader_id)
4391    );
4392
4393    // When client B edits, it automatically stops following client A.
4394    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
4395    assert_eq!(
4396        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4397        None
4398    );
4399
4400    workspace_b
4401        .update(cx_b, |workspace, cx| {
4402            workspace
4403                .toggle_follow(&ToggleFollow(leader_id), cx)
4404                .unwrap()
4405        })
4406        .await
4407        .unwrap();
4408    assert_eq!(
4409        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4410        Some(leader_id)
4411    );
4412
4413    // When client B scrolls, it automatically stops following client A.
4414    editor_b2.update(cx_b, |editor, cx| {
4415        editor.set_scroll_position(vec2f(0., 3.), cx)
4416    });
4417    assert_eq!(
4418        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4419        None
4420    );
4421
4422    workspace_b
4423        .update(cx_b, |workspace, cx| {
4424            workspace
4425                .toggle_follow(&ToggleFollow(leader_id), cx)
4426                .unwrap()
4427        })
4428        .await
4429        .unwrap();
4430    assert_eq!(
4431        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4432        Some(leader_id)
4433    );
4434
4435    // When client B activates a different pane, it continues following client A in the original pane.
4436    workspace_b.update(cx_b, |workspace, cx| {
4437        workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
4438    });
4439    assert_eq!(
4440        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4441        Some(leader_id)
4442    );
4443
4444    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
4445    assert_eq!(
4446        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4447        Some(leader_id)
4448    );
4449
4450    // When client B activates a different item in the original pane, it automatically stops following client A.
4451    workspace_b
4452        .update(cx_b, |workspace, cx| {
4453            workspace.open_path((worktree_id, "2.txt"), true, cx)
4454        })
4455        .await
4456        .unwrap();
4457    assert_eq!(
4458        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
4459        None
4460    );
4461}
4462
4463#[gpui::test(iterations = 10)]
4464async fn test_peers_simultaneously_following_each_other(
4465    deterministic: Arc<Deterministic>,
4466    cx_a: &mut TestAppContext,
4467    cx_b: &mut TestAppContext,
4468) {
4469    deterministic.forbid_parking();
4470
4471    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4472    let client_a = server.create_client(cx_a, "user_a").await;
4473    let client_b = server.create_client(cx_b, "user_b").await;
4474    server
4475        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
4476        .await;
4477    cx_a.update(editor::init);
4478    cx_b.update(editor::init);
4479
4480    client_a.fs.insert_tree("/a", json!({})).await;
4481    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
4482    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4483
4484    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
4485    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4486
4487    deterministic.run_until_parked();
4488    let client_a_id = project_b.read_with(cx_b, |project, _| {
4489        project.collaborators().values().next().unwrap().peer_id
4490    });
4491    let client_b_id = project_a.read_with(cx_a, |project, _| {
4492        project.collaborators().values().next().unwrap().peer_id
4493    });
4494
4495    let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
4496        workspace
4497            .toggle_follow(&ToggleFollow(client_b_id), cx)
4498            .unwrap()
4499    });
4500    let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
4501        workspace
4502            .toggle_follow(&ToggleFollow(client_a_id), cx)
4503            .unwrap()
4504    });
4505
4506    futures::try_join!(a_follow_b, b_follow_a).unwrap();
4507    workspace_a.read_with(cx_a, |workspace, _| {
4508        assert_eq!(
4509            workspace.leader_for_pane(workspace.active_pane()),
4510            Some(client_b_id)
4511        );
4512    });
4513    workspace_b.read_with(cx_b, |workspace, _| {
4514        assert_eq!(
4515            workspace.leader_for_pane(workspace.active_pane()),
4516            Some(client_a_id)
4517        );
4518    });
4519}
4520
4521#[gpui::test(iterations = 100)]
4522async fn test_random_collaboration(
4523    cx: &mut TestAppContext,
4524    deterministic: Arc<Deterministic>,
4525    rng: StdRng,
4526) {
4527    deterministic.forbid_parking();
4528    let max_peers = env::var("MAX_PEERS")
4529        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
4530        .unwrap_or(5);
4531    assert!(max_peers <= 5);
4532
4533    let max_operations = env::var("OPERATIONS")
4534        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
4535        .unwrap_or(10);
4536
4537    let rng = Arc::new(Mutex::new(rng));
4538
4539    let guest_lang_registry = Arc::new(LanguageRegistry::test());
4540    let host_language_registry = Arc::new(LanguageRegistry::test());
4541
4542    let fs = FakeFs::new(cx.background());
4543    fs.insert_tree("/_collab", json!({"init": ""})).await;
4544
4545    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
4546    let db = server.app_state.db.clone();
4547    let host_user_id = db.create_user("host", None, false).await.unwrap();
4548    let mut available_guests = vec![
4549        "guest-1".to_string(),
4550        "guest-2".to_string(),
4551        "guest-3".to_string(),
4552        "guest-4".to_string(),
4553    ];
4554
4555    for username in &available_guests {
4556        let guest_user_id = db.create_user(username, None, false).await.unwrap();
4557        assert_eq!(*username, format!("guest-{}", guest_user_id));
4558        server
4559            .app_state
4560            .db
4561            .send_contact_request(guest_user_id, host_user_id)
4562            .await
4563            .unwrap();
4564        server
4565            .app_state
4566            .db
4567            .respond_to_contact_request(host_user_id, guest_user_id, true)
4568            .await
4569            .unwrap();
4570    }
4571
4572    let mut clients = Vec::new();
4573    let mut user_ids = Vec::new();
4574    let mut op_start_signals = Vec::new();
4575
4576    let mut next_entity_id = 100000;
4577    let mut host_cx = TestAppContext::new(
4578        cx.foreground_platform(),
4579        cx.platform(),
4580        deterministic.build_foreground(next_entity_id),
4581        deterministic.build_background(),
4582        cx.font_cache(),
4583        cx.leak_detector(),
4584        next_entity_id,
4585    );
4586    let host = server.create_client(&mut host_cx, "host").await;
4587    let host_project = host_cx.update(|cx| {
4588        Project::local(
4589            true,
4590            host.client.clone(),
4591            host.user_store.clone(),
4592            host.project_store.clone(),
4593            host_language_registry.clone(),
4594            fs.clone(),
4595            cx,
4596        )
4597    });
4598    let host_project_id = host_project
4599        .update(&mut host_cx, |p, _| p.next_remote_id())
4600        .await;
4601
4602    let (collab_worktree, _) = host_project
4603        .update(&mut host_cx, |project, cx| {
4604            project.find_or_create_local_worktree("/_collab", true, cx)
4605        })
4606        .await
4607        .unwrap();
4608    collab_worktree
4609        .read_with(&host_cx, |tree, _| tree.as_local().unwrap().scan_complete())
4610        .await;
4611
4612    // Set up fake language servers.
4613    let mut language = Language::new(
4614        LanguageConfig {
4615            name: "Rust".into(),
4616            path_suffixes: vec!["rs".to_string()],
4617            ..Default::default()
4618        },
4619        None,
4620    );
4621    let _fake_servers = language
4622        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4623            name: "the-fake-language-server",
4624            capabilities: lsp::LanguageServer::full_capabilities(),
4625            initializer: Some(Box::new({
4626                let rng = rng.clone();
4627                let fs = fs.clone();
4628                let project = host_project.downgrade();
4629                move |fake_server: &mut FakeLanguageServer| {
4630                    fake_server.handle_request::<lsp::request::Completion, _, _>(
4631                        |_, _| async move {
4632                            Ok(Some(lsp::CompletionResponse::Array(vec![
4633                                lsp::CompletionItem {
4634                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
4635                                        range: lsp::Range::new(
4636                                            lsp::Position::new(0, 0),
4637                                            lsp::Position::new(0, 0),
4638                                        ),
4639                                        new_text: "the-new-text".to_string(),
4640                                    })),
4641                                    ..Default::default()
4642                                },
4643                            ])))
4644                        },
4645                    );
4646
4647                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
4648                        |_, _| async move {
4649                            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
4650                                lsp::CodeAction {
4651                                    title: "the-code-action".to_string(),
4652                                    ..Default::default()
4653                                },
4654                            )]))
4655                        },
4656                    );
4657
4658                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
4659                        |params, _| async move {
4660                            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
4661                                params.position,
4662                                params.position,
4663                            ))))
4664                        },
4665                    );
4666
4667                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
4668                        let fs = fs.clone();
4669                        let rng = rng.clone();
4670                        move |_, _| {
4671                            let fs = fs.clone();
4672                            let rng = rng.clone();
4673                            async move {
4674                                let files = fs.files().await;
4675                                let mut rng = rng.lock();
4676                                let count = rng.gen_range::<usize, _>(1..3);
4677                                let files = (0..count)
4678                                    .map(|_| files.choose(&mut *rng).unwrap())
4679                                    .collect::<Vec<_>>();
4680                                log::info!("LSP: Returning definitions in files {:?}", &files);
4681                                Ok(Some(lsp::GotoDefinitionResponse::Array(
4682                                    files
4683                                        .into_iter()
4684                                        .map(|file| lsp::Location {
4685                                            uri: lsp::Url::from_file_path(file).unwrap(),
4686                                            range: Default::default(),
4687                                        })
4688                                        .collect(),
4689                                )))
4690                            }
4691                        }
4692                    });
4693
4694                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
4695                        let rng = rng.clone();
4696                        let project = project;
4697                        move |params, mut cx| {
4698                            let highlights = if let Some(project) = project.upgrade(&cx) {
4699                                project.update(&mut cx, |project, cx| {
4700                                    let path = params
4701                                        .text_document_position_params
4702                                        .text_document
4703                                        .uri
4704                                        .to_file_path()
4705                                        .unwrap();
4706                                    let (worktree, relative_path) =
4707                                        project.find_local_worktree(&path, cx)?;
4708                                    let project_path =
4709                                        ProjectPath::from((worktree.read(cx).id(), relative_path));
4710                                    let buffer =
4711                                        project.get_open_buffer(&project_path, cx)?.read(cx);
4712
4713                                    let mut highlights = Vec::new();
4714                                    let highlight_count = rng.lock().gen_range(1..=5);
4715                                    let mut prev_end = 0;
4716                                    for _ in 0..highlight_count {
4717                                        let range =
4718                                            buffer.random_byte_range(prev_end, &mut *rng.lock());
4719
4720                                        highlights.push(lsp::DocumentHighlight {
4721                                            range: range_to_lsp(range.to_point_utf16(buffer)),
4722                                            kind: Some(lsp::DocumentHighlightKind::READ),
4723                                        });
4724                                        prev_end = range.end;
4725                                    }
4726                                    Some(highlights)
4727                                })
4728                            } else {
4729                                None
4730                            };
4731                            async move { Ok(highlights) }
4732                        }
4733                    });
4734                }
4735            })),
4736            ..Default::default()
4737        }))
4738        .await;
4739    host_language_registry.add(Arc::new(language));
4740
4741    let op_start_signal = futures::channel::mpsc::unbounded();
4742    user_ids.push(host.current_user_id(&host_cx));
4743    op_start_signals.push(op_start_signal.0);
4744    clients.push(host_cx.foreground().spawn(host.simulate_host(
4745        host_project,
4746        op_start_signal.1,
4747        rng.clone(),
4748        host_cx,
4749    )));
4750
4751    let disconnect_host_at = if rng.lock().gen_bool(0.2) {
4752        rng.lock().gen_range(0..max_operations)
4753    } else {
4754        max_operations
4755    };
4756
4757    let mut operations = 0;
4758    while operations < max_operations {
4759        if operations == disconnect_host_at {
4760            server.disconnect_client(user_ids[0]);
4761            deterministic.advance_clock(RECEIVE_TIMEOUT);
4762            drop(op_start_signals);
4763
4764            deterministic.start_waiting();
4765            let mut clients = futures::future::join_all(clients).await;
4766            deterministic.finish_waiting();
4767            deterministic.run_until_parked();
4768
4769            let (host, host_project, mut host_cx, host_err) = clients.remove(0);
4770            if let Some(host_err) = host_err {
4771                log::error!("host error - {:?}", host_err);
4772            }
4773            host_project.read_with(&host_cx, |project, _| assert!(!project.is_shared()));
4774            for (guest, guest_project, mut guest_cx, guest_err) in clients {
4775                if let Some(guest_err) = guest_err {
4776                    log::error!("{} error - {:?}", guest.username, guest_err);
4777                }
4778
4779                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4780                guest_cx.update(|_| drop((guest, guest_project)));
4781            }
4782            host_cx.update(|_| drop((host, host_project)));
4783
4784            return;
4785        }
4786
4787        let distribution = rng.lock().gen_range(0..100);
4788        match distribution {
4789            0..=19 if !available_guests.is_empty() => {
4790                let guest_ix = rng.lock().gen_range(0..available_guests.len());
4791                let guest_username = available_guests.remove(guest_ix);
4792                log::info!("Adding new connection for {}", guest_username);
4793                next_entity_id += 100000;
4794                let mut guest_cx = TestAppContext::new(
4795                    cx.foreground_platform(),
4796                    cx.platform(),
4797                    deterministic.build_foreground(next_entity_id),
4798                    deterministic.build_background(),
4799                    cx.font_cache(),
4800                    cx.leak_detector(),
4801                    next_entity_id,
4802                );
4803
4804                deterministic.start_waiting();
4805                let guest = server.create_client(&mut guest_cx, &guest_username).await;
4806                let guest_project = Project::remote(
4807                    host_project_id,
4808                    guest.client.clone(),
4809                    guest.user_store.clone(),
4810                    guest.project_store.clone(),
4811                    guest_lang_registry.clone(),
4812                    FakeFs::new(cx.background()),
4813                    guest_cx.to_async(),
4814                )
4815                .await
4816                .unwrap();
4817                deterministic.finish_waiting();
4818
4819                let op_start_signal = futures::channel::mpsc::unbounded();
4820                user_ids.push(guest.current_user_id(&guest_cx));
4821                op_start_signals.push(op_start_signal.0);
4822                clients.push(guest_cx.foreground().spawn(guest.simulate_guest(
4823                    guest_username.clone(),
4824                    guest_project,
4825                    op_start_signal.1,
4826                    rng.clone(),
4827                    guest_cx,
4828                )));
4829
4830                log::info!("Added connection for {}", guest_username);
4831                operations += 1;
4832            }
4833            20..=29 if clients.len() > 1 => {
4834                let guest_ix = rng.lock().gen_range(1..clients.len());
4835                log::info!("Removing guest {}", user_ids[guest_ix]);
4836                let removed_guest_id = user_ids.remove(guest_ix);
4837                let guest = clients.remove(guest_ix);
4838                op_start_signals.remove(guest_ix);
4839                server.forbid_connections();
4840                server.disconnect_client(removed_guest_id);
4841                deterministic.advance_clock(RECEIVE_TIMEOUT);
4842                deterministic.start_waiting();
4843                log::info!("Waiting for guest {} to exit...", removed_guest_id);
4844                let (guest, guest_project, mut guest_cx, guest_err) = guest.await;
4845                deterministic.finish_waiting();
4846                server.allow_connections();
4847
4848                if let Some(guest_err) = guest_err {
4849                    log::error!("{} error - {:?}", guest.username, guest_err);
4850                }
4851                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
4852                for user_id in &user_ids {
4853                    let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
4854                    let contacts = server
4855                        .store
4856                        .lock()
4857                        .await
4858                        .build_initial_contacts_update(contacts)
4859                        .contacts;
4860                    for contact in contacts {
4861                        if contact.online {
4862                            assert_ne!(
4863                                contact.user_id, removed_guest_id.0 as u64,
4864                                "removed guest is still a contact of another peer"
4865                            );
4866                        }
4867                    }
4868                }
4869
4870                log::info!("{} removed", guest.username);
4871                available_guests.push(guest.username.clone());
4872                guest_cx.update(|_| drop((guest, guest_project)));
4873
4874                operations += 1;
4875            }
4876            _ => {
4877                while operations < max_operations && rng.lock().gen_bool(0.7) {
4878                    op_start_signals
4879                        .choose(&mut *rng.lock())
4880                        .unwrap()
4881                        .unbounded_send(())
4882                        .unwrap();
4883                    operations += 1;
4884                }
4885
4886                if rng.lock().gen_bool(0.8) {
4887                    deterministic.run_until_parked();
4888                }
4889            }
4890        }
4891    }
4892
4893    drop(op_start_signals);
4894    deterministic.start_waiting();
4895    let mut clients = futures::future::join_all(clients).await;
4896    deterministic.finish_waiting();
4897    deterministic.run_until_parked();
4898
4899    let (host_client, host_project, mut host_cx, host_err) = clients.remove(0);
4900    if let Some(host_err) = host_err {
4901        panic!("host error - {:?}", host_err);
4902    }
4903    let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
4904        project
4905            .worktrees(cx)
4906            .map(|worktree| {
4907                let snapshot = worktree.read(cx).snapshot();
4908                (snapshot.id(), snapshot)
4909            })
4910            .collect::<BTreeMap<_, _>>()
4911    });
4912
4913    host_project.read_with(&host_cx, |project, cx| project.check_invariants(cx));
4914
4915    for (guest_client, guest_project, mut guest_cx, guest_err) in clients.into_iter() {
4916        if let Some(guest_err) = guest_err {
4917            panic!("{} error - {:?}", guest_client.username, guest_err);
4918        }
4919        let worktree_snapshots = guest_project.read_with(&guest_cx, |project, cx| {
4920            project
4921                .worktrees(cx)
4922                .map(|worktree| {
4923                    let worktree = worktree.read(cx);
4924                    (worktree.id(), worktree.snapshot())
4925                })
4926                .collect::<BTreeMap<_, _>>()
4927        });
4928
4929        assert_eq!(
4930            worktree_snapshots.keys().collect::<Vec<_>>(),
4931            host_worktree_snapshots.keys().collect::<Vec<_>>(),
4932            "{} has different worktrees than the host",
4933            guest_client.username
4934        );
4935        for (id, host_snapshot) in &host_worktree_snapshots {
4936            let guest_snapshot = &worktree_snapshots[id];
4937            assert_eq!(
4938                guest_snapshot.root_name(),
4939                host_snapshot.root_name(),
4940                "{} has different root name than the host for worktree {}",
4941                guest_client.username,
4942                id
4943            );
4944            assert_eq!(
4945                guest_snapshot.entries(false).collect::<Vec<_>>(),
4946                host_snapshot.entries(false).collect::<Vec<_>>(),
4947                "{} has different snapshot than the host for worktree {}",
4948                guest_client.username,
4949                id
4950            );
4951            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
4952        }
4953
4954        guest_project.read_with(&guest_cx, |project, cx| project.check_invariants(cx));
4955
4956        for guest_buffer in &guest_client.buffers {
4957            let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
4958            let host_buffer = host_project.read_with(&host_cx, |project, cx| {
4959                project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
4960                    panic!(
4961                        "host does not have buffer for guest:{}, peer:{}, id:{}",
4962                        guest_client.username, guest_client.peer_id, buffer_id
4963                    )
4964                })
4965            });
4966            let path =
4967                host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
4968
4969            assert_eq!(
4970                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
4971                0,
4972                "{}, buffer {}, path {:?} has deferred operations",
4973                guest_client.username,
4974                buffer_id,
4975                path,
4976            );
4977            assert_eq!(
4978                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
4979                host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
4980                "{}, buffer {}, path {:?}, differs from the host's buffer",
4981                guest_client.username,
4982                buffer_id,
4983                path
4984            );
4985        }
4986
4987        guest_cx.update(|_| drop((guest_project, guest_client)));
4988    }
4989
4990    host_cx.update(|_| drop((host_client, host_project)));
4991}
4992
4993struct TestServer {
4994    peer: Arc<Peer>,
4995    app_state: Arc<AppState>,
4996    server: Arc<Server>,
4997    foreground: Rc<executor::Foreground>,
4998    notifications: mpsc::UnboundedReceiver<()>,
4999    connection_killers: Arc<Mutex<HashMap<UserId, Arc<AtomicBool>>>>,
5000    forbid_connections: Arc<AtomicBool>,
5001    _test_db: TestDb,
5002}
5003
5004impl TestServer {
5005    async fn start(
5006        foreground: Rc<executor::Foreground>,
5007        background: Arc<executor::Background>,
5008    ) -> Self {
5009        let test_db = TestDb::fake(background.clone());
5010        let app_state = Self::build_app_state(&test_db).await;
5011        let peer = Peer::new();
5012        let notifications = mpsc::unbounded();
5013        let server = Server::new(app_state.clone(), Some(notifications.0));
5014        Self {
5015            peer,
5016            app_state,
5017            server,
5018            foreground,
5019            notifications: notifications.1,
5020            connection_killers: Default::default(),
5021            forbid_connections: Default::default(),
5022            _test_db: test_db,
5023        }
5024    }
5025
5026    async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
5027        cx.update(|cx| {
5028            let mut settings = Settings::test(cx);
5029            settings.projects_online_by_default = false;
5030            cx.set_global(settings);
5031        });
5032
5033        let http = FakeHttpClient::with_404_response();
5034        let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await
5035        {
5036            user.id
5037        } else {
5038            self.app_state
5039                .db
5040                .create_user(name, None, false)
5041                .await
5042                .unwrap()
5043        };
5044        let client_name = name.to_string();
5045        let mut client = Client::new(http.clone());
5046        let server = self.server.clone();
5047        let db = self.app_state.db.clone();
5048        let connection_killers = self.connection_killers.clone();
5049        let forbid_connections = self.forbid_connections.clone();
5050        let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16);
5051
5052        Arc::get_mut(&mut client)
5053            .unwrap()
5054            .set_id(user_id.0 as usize)
5055            .override_authenticate(move |cx| {
5056                cx.spawn(|_| async move {
5057                    let access_token = "the-token".to_string();
5058                    Ok(Credentials {
5059                        user_id: user_id.0 as u64,
5060                        access_token,
5061                    })
5062                })
5063            })
5064            .override_establish_connection(move |credentials, cx| {
5065                assert_eq!(credentials.user_id, user_id.0 as u64);
5066                assert_eq!(credentials.access_token, "the-token");
5067
5068                let server = server.clone();
5069                let db = db.clone();
5070                let connection_killers = connection_killers.clone();
5071                let forbid_connections = forbid_connections.clone();
5072                let client_name = client_name.clone();
5073                let connection_id_tx = connection_id_tx.clone();
5074                cx.spawn(move |cx| async move {
5075                    if forbid_connections.load(SeqCst) {
5076                        Err(EstablishConnectionError::other(anyhow!(
5077                            "server is forbidding connections"
5078                        )))
5079                    } else {
5080                        let (client_conn, server_conn, killed) =
5081                            Connection::in_memory(cx.background());
5082                        connection_killers.lock().insert(user_id, killed);
5083                        let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
5084                        cx.background()
5085                            .spawn(server.handle_connection(
5086                                server_conn,
5087                                client_name,
5088                                user,
5089                                Some(connection_id_tx),
5090                                cx.background(),
5091                            ))
5092                            .detach();
5093                        Ok(client_conn)
5094                    }
5095                })
5096            });
5097
5098        let fs = FakeFs::new(cx.background());
5099        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
5100        let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
5101        let app_state = Arc::new(workspace::AppState {
5102            client: client.clone(),
5103            user_store: user_store.clone(),
5104            project_store: project_store.clone(),
5105            languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
5106            themes: ThemeRegistry::new((), cx.font_cache()),
5107            fs: fs.clone(),
5108            build_window_options: Default::default,
5109            initialize_workspace: |_, _, _| unimplemented!(),
5110            default_item_factory: |_, _| unimplemented!(),
5111        });
5112
5113        Channel::init(&client);
5114        Project::init(&client);
5115        cx.update(|cx| workspace::init(app_state.clone(), cx));
5116
5117        client
5118            .authenticate_and_connect(false, &cx.to_async())
5119            .await
5120            .unwrap();
5121        let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
5122
5123        let client = TestClient {
5124            client,
5125            peer_id,
5126            username: name.to_string(),
5127            user_store,
5128            project_store,
5129            fs,
5130            language_registry: Arc::new(LanguageRegistry::test()),
5131            buffers: Default::default(),
5132        };
5133        client.wait_for_current_user(cx).await;
5134        client
5135    }
5136
5137    fn disconnect_client(&self, user_id: UserId) {
5138        self.connection_killers
5139            .lock()
5140            .remove(&user_id)
5141            .unwrap()
5142            .store(true, SeqCst);
5143    }
5144
5145    fn forbid_connections(&self) {
5146        self.forbid_connections.store(true, SeqCst);
5147    }
5148
5149    fn allow_connections(&self) {
5150        self.forbid_connections.store(false, SeqCst);
5151    }
5152
5153    async fn make_contacts(&self, mut clients: Vec<(&TestClient, &mut TestAppContext)>) {
5154        while let Some((client_a, cx_a)) = clients.pop() {
5155            for (client_b, cx_b) in &mut clients {
5156                client_a
5157                    .user_store
5158                    .update(cx_a, |store, cx| {
5159                        store.request_contact(client_b.user_id().unwrap(), cx)
5160                    })
5161                    .await
5162                    .unwrap();
5163                cx_a.foreground().run_until_parked();
5164                client_b
5165                    .user_store
5166                    .update(*cx_b, |store, cx| {
5167                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
5168                    })
5169                    .await
5170                    .unwrap();
5171            }
5172        }
5173    }
5174
5175    async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
5176        Arc::new(AppState {
5177            db: test_db.db().clone(),
5178            api_token: Default::default(),
5179            invite_link_prefix: Default::default(),
5180        })
5181    }
5182
5183    async fn condition<F>(&mut self, mut predicate: F)
5184    where
5185        F: FnMut(&Store) -> bool,
5186    {
5187        assert!(
5188            self.foreground.parking_forbidden(),
5189            "you must call forbid_parking to use server conditions so we don't block indefinitely"
5190        );
5191        while !(predicate)(&*self.server.store.lock().await) {
5192            self.foreground.start_waiting();
5193            self.notifications.next().await;
5194            self.foreground.finish_waiting();
5195        }
5196    }
5197}
5198
5199impl Deref for TestServer {
5200    type Target = Server;
5201
5202    fn deref(&self) -> &Self::Target {
5203        &self.server
5204    }
5205}
5206
5207impl Drop for TestServer {
5208    fn drop(&mut self) {
5209        self.peer.reset();
5210    }
5211}
5212
5213struct TestClient {
5214    client: Arc<Client>,
5215    username: String,
5216    pub peer_id: PeerId,
5217    pub user_store: ModelHandle<UserStore>,
5218    pub project_store: ModelHandle<ProjectStore>,
5219    language_registry: Arc<LanguageRegistry>,
5220    fs: Arc<FakeFs>,
5221    buffers: HashSet<ModelHandle<language::Buffer>>,
5222}
5223
5224impl Deref for TestClient {
5225    type Target = Arc<Client>;
5226
5227    fn deref(&self) -> &Self::Target {
5228        &self.client
5229    }
5230}
5231
5232struct ContactsSummary {
5233    pub current: Vec<String>,
5234    pub outgoing_requests: Vec<String>,
5235    pub incoming_requests: Vec<String>,
5236}
5237
5238impl TestClient {
5239    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
5240        UserId::from_proto(
5241            self.user_store
5242                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
5243        )
5244    }
5245
5246    async fn wait_for_current_user(&self, cx: &TestAppContext) {
5247        let mut authed_user = self
5248            .user_store
5249            .read_with(cx, |user_store, _| user_store.watch_current_user());
5250        while authed_user.next().await.unwrap().is_none() {}
5251    }
5252
5253    async fn clear_contacts(&self, cx: &mut TestAppContext) {
5254        self.user_store
5255            .update(cx, |store, _| store.clear_contacts())
5256            .await;
5257    }
5258
5259    fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
5260        self.user_store.read_with(cx, |store, _| ContactsSummary {
5261            current: store
5262                .contacts()
5263                .iter()
5264                .map(|contact| contact.user.github_login.clone())
5265                .collect(),
5266            outgoing_requests: store
5267                .outgoing_contact_requests()
5268                .iter()
5269                .map(|user| user.github_login.clone())
5270                .collect(),
5271            incoming_requests: store
5272                .incoming_contact_requests()
5273                .iter()
5274                .map(|user| user.github_login.clone())
5275                .collect(),
5276        })
5277    }
5278
5279    async fn build_local_project(
5280        &self,
5281        root_path: impl AsRef<Path>,
5282        cx: &mut TestAppContext,
5283    ) -> (ModelHandle<Project>, WorktreeId) {
5284        let project = cx.update(|cx| {
5285            Project::local(
5286                true,
5287                self.client.clone(),
5288                self.user_store.clone(),
5289                self.project_store.clone(),
5290                self.language_registry.clone(),
5291                self.fs.clone(),
5292                cx,
5293            )
5294        });
5295        let (worktree, _) = project
5296            .update(cx, |p, cx| {
5297                p.find_or_create_local_worktree(root_path, true, cx)
5298            })
5299            .await
5300            .unwrap();
5301        worktree
5302            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
5303            .await;
5304        project
5305            .update(cx, |project, _| project.next_remote_id())
5306            .await;
5307        (project, worktree.read_with(cx, |tree, _| tree.id()))
5308    }
5309
5310    async fn build_remote_project(
5311        &self,
5312        host_project: &ModelHandle<Project>,
5313        host_cx: &mut TestAppContext,
5314        guest_cx: &mut TestAppContext,
5315    ) -> ModelHandle<Project> {
5316        let host_project_id = host_project
5317            .read_with(host_cx, |project, _| project.next_remote_id())
5318            .await;
5319        let guest_user_id = self.user_id().unwrap();
5320        let languages = host_project.read_with(host_cx, |project, _| project.languages().clone());
5321        let project_b = guest_cx.spawn(|cx| {
5322            Project::remote(
5323                host_project_id,
5324                self.client.clone(),
5325                self.user_store.clone(),
5326                self.project_store.clone(),
5327                languages,
5328                FakeFs::new(cx.background()),
5329                cx,
5330            )
5331        });
5332        host_cx.foreground().run_until_parked();
5333        host_project.update(host_cx, |project, cx| {
5334            project.respond_to_join_request(guest_user_id, true, cx)
5335        });
5336        let project = project_b.await.unwrap();
5337        project
5338    }
5339
5340    fn build_workspace(
5341        &self,
5342        project: &ModelHandle<Project>,
5343        cx: &mut TestAppContext,
5344    ) -> ViewHandle<Workspace> {
5345        let (_, root_view) = cx.add_window(|_| EmptyView);
5346        cx.add_view(&root_view, |cx| {
5347            Workspace::new(project.clone(), |_, _| unimplemented!(), cx)
5348        })
5349    }
5350
5351    async fn simulate_host(
5352        mut self,
5353        project: ModelHandle<Project>,
5354        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5355        rng: Arc<Mutex<StdRng>>,
5356        mut cx: TestAppContext,
5357    ) -> (
5358        Self,
5359        ModelHandle<Project>,
5360        TestAppContext,
5361        Option<anyhow::Error>,
5362    ) {
5363        async fn simulate_host_internal(
5364            client: &mut TestClient,
5365            project: ModelHandle<Project>,
5366            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5367            rng: Arc<Mutex<StdRng>>,
5368            cx: &mut TestAppContext,
5369        ) -> anyhow::Result<()> {
5370            let fs = project.read_with(cx, |project, _| project.fs().clone());
5371
5372            cx.update(|cx| {
5373                cx.subscribe(&project, move |project, event, cx| {
5374                    if let project::Event::ContactRequestedJoin(user) = event {
5375                        log::info!("Host: accepting join request from {}", user.github_login);
5376                        project.update(cx, |project, cx| {
5377                            project.respond_to_join_request(user.id, true, cx)
5378                        });
5379                    }
5380                })
5381                .detach();
5382            });
5383
5384            while op_start_signal.next().await.is_some() {
5385                let distribution = rng.lock().gen_range::<usize, _>(0..100);
5386                let files = fs.as_fake().files().await;
5387                match distribution {
5388                    0..=19 if !files.is_empty() => {
5389                        let path = files.choose(&mut *rng.lock()).unwrap();
5390                        let mut path = path.as_path();
5391                        while let Some(parent_path) = path.parent() {
5392                            path = parent_path;
5393                            if rng.lock().gen() {
5394                                break;
5395                            }
5396                        }
5397
5398                        log::info!("Host: find/create local worktree {:?}", path);
5399                        let find_or_create_worktree = project.update(cx, |project, cx| {
5400                            project.find_or_create_local_worktree(path, true, cx)
5401                        });
5402                        if rng.lock().gen() {
5403                            cx.background().spawn(find_or_create_worktree).detach();
5404                        } else {
5405                            find_or_create_worktree.await?;
5406                        }
5407                    }
5408                    20..=79 if !files.is_empty() => {
5409                        let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5410                            let file = files.choose(&mut *rng.lock()).unwrap();
5411                            let (worktree, path) = project
5412                                .update(cx, |project, cx| {
5413                                    project.find_or_create_local_worktree(file.clone(), true, cx)
5414                                })
5415                                .await?;
5416                            let project_path =
5417                                worktree.read_with(cx, |worktree, _| (worktree.id(), path));
5418                            log::info!(
5419                                "Host: opening path {:?}, worktree {}, relative_path {:?}",
5420                                file,
5421                                project_path.0,
5422                                project_path.1
5423                            );
5424                            let buffer = project
5425                                .update(cx, |project, cx| project.open_buffer(project_path, cx))
5426                                .await
5427                                .unwrap();
5428                            client.buffers.insert(buffer.clone());
5429                            buffer
5430                        } else {
5431                            client
5432                                .buffers
5433                                .iter()
5434                                .choose(&mut *rng.lock())
5435                                .unwrap()
5436                                .clone()
5437                        };
5438
5439                        if rng.lock().gen_bool(0.1) {
5440                            cx.update(|cx| {
5441                                log::info!(
5442                                    "Host: dropping buffer {:?}",
5443                                    buffer.read(cx).file().unwrap().full_path(cx)
5444                                );
5445                                client.buffers.remove(&buffer);
5446                                drop(buffer);
5447                            });
5448                        } else {
5449                            buffer.update(cx, |buffer, cx| {
5450                                log::info!(
5451                                    "Host: updating buffer {:?} ({})",
5452                                    buffer.file().unwrap().full_path(cx),
5453                                    buffer.remote_id()
5454                                );
5455
5456                                if rng.lock().gen_bool(0.7) {
5457                                    buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5458                                } else {
5459                                    buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5460                                }
5461                            });
5462                        }
5463                    }
5464                    _ => loop {
5465                        let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
5466                        let mut path = PathBuf::new();
5467                        path.push("/");
5468                        for _ in 0..path_component_count {
5469                            let letter = rng.lock().gen_range(b'a'..=b'z');
5470                            path.push(std::str::from_utf8(&[letter]).unwrap());
5471                        }
5472                        path.set_extension("rs");
5473                        let parent_path = path.parent().unwrap();
5474
5475                        log::info!("Host: creating file {:?}", path,);
5476
5477                        if fs.create_dir(parent_path).await.is_ok()
5478                            && fs.create_file(&path, Default::default()).await.is_ok()
5479                        {
5480                            break;
5481                        } else {
5482                            log::info!("Host: cannot create file");
5483                        }
5484                    },
5485                }
5486
5487                cx.background().simulate_random_delay().await;
5488            }
5489
5490            Ok(())
5491        }
5492
5493        let result =
5494            simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
5495        log::info!("Host done");
5496        (self, project, cx, result.err())
5497    }
5498
5499    pub async fn simulate_guest(
5500        mut self,
5501        guest_username: String,
5502        project: ModelHandle<Project>,
5503        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5504        rng: Arc<Mutex<StdRng>>,
5505        mut cx: TestAppContext,
5506    ) -> (
5507        Self,
5508        ModelHandle<Project>,
5509        TestAppContext,
5510        Option<anyhow::Error>,
5511    ) {
5512        async fn simulate_guest_internal(
5513            client: &mut TestClient,
5514            guest_username: &str,
5515            project: ModelHandle<Project>,
5516            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
5517            rng: Arc<Mutex<StdRng>>,
5518            cx: &mut TestAppContext,
5519        ) -> anyhow::Result<()> {
5520            while op_start_signal.next().await.is_some() {
5521                let buffer = if client.buffers.is_empty() || rng.lock().gen() {
5522                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
5523                        project
5524                            .worktrees(cx)
5525                            .filter(|worktree| {
5526                                let worktree = worktree.read(cx);
5527                                worktree.is_visible()
5528                                    && worktree.entries(false).any(|e| e.is_file())
5529                            })
5530                            .choose(&mut *rng.lock())
5531                    }) {
5532                        worktree
5533                    } else {
5534                        cx.background().simulate_random_delay().await;
5535                        continue;
5536                    };
5537
5538                    let (worktree_root_name, project_path) =
5539                        worktree.read_with(cx, |worktree, _| {
5540                            let entry = worktree
5541                                .entries(false)
5542                                .filter(|e| e.is_file())
5543                                .choose(&mut *rng.lock())
5544                                .unwrap();
5545                            (
5546                                worktree.root_name().to_string(),
5547                                (worktree.id(), entry.path.clone()),
5548                            )
5549                        });
5550                    log::info!(
5551                        "{}: opening path {:?} in worktree {} ({})",
5552                        guest_username,
5553                        project_path.1,
5554                        project_path.0,
5555                        worktree_root_name,
5556                    );
5557                    let buffer = project
5558                        .update(cx, |project, cx| {
5559                            project.open_buffer(project_path.clone(), cx)
5560                        })
5561                        .await?;
5562                    log::info!(
5563                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
5564                        guest_username,
5565                        project_path.1,
5566                        project_path.0,
5567                        worktree_root_name,
5568                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
5569                    );
5570                    client.buffers.insert(buffer.clone());
5571                    buffer
5572                } else {
5573                    client
5574                        .buffers
5575                        .iter()
5576                        .choose(&mut *rng.lock())
5577                        .unwrap()
5578                        .clone()
5579                };
5580
5581                let choice = rng.lock().gen_range(0..100);
5582                match choice {
5583                    0..=9 => {
5584                        cx.update(|cx| {
5585                            log::info!(
5586                                "{}: dropping buffer {:?}",
5587                                guest_username,
5588                                buffer.read(cx).file().unwrap().full_path(cx)
5589                            );
5590                            client.buffers.remove(&buffer);
5591                            drop(buffer);
5592                        });
5593                    }
5594                    10..=19 => {
5595                        let completions = project.update(cx, |project, cx| {
5596                            log::info!(
5597                                "{}: requesting completions for buffer {} ({:?})",
5598                                guest_username,
5599                                buffer.read(cx).remote_id(),
5600                                buffer.read(cx).file().unwrap().full_path(cx)
5601                            );
5602                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5603                            project.completions(&buffer, offset, cx)
5604                        });
5605                        let completions = cx.background().spawn(async move {
5606                            completions
5607                                .await
5608                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
5609                        });
5610                        if rng.lock().gen_bool(0.3) {
5611                            log::info!("{}: detaching completions request", guest_username);
5612                            cx.update(|cx| completions.detach_and_log_err(cx));
5613                        } else {
5614                            completions.await?;
5615                        }
5616                    }
5617                    20..=29 => {
5618                        let code_actions = project.update(cx, |project, cx| {
5619                            log::info!(
5620                                "{}: requesting code actions for buffer {} ({:?})",
5621                                guest_username,
5622                                buffer.read(cx).remote_id(),
5623                                buffer.read(cx).file().unwrap().full_path(cx)
5624                            );
5625                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
5626                            project.code_actions(&buffer, range, cx)
5627                        });
5628                        let code_actions = cx.background().spawn(async move {
5629                            code_actions
5630                                .await
5631                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
5632                        });
5633                        if rng.lock().gen_bool(0.3) {
5634                            log::info!("{}: detaching code actions request", guest_username);
5635                            cx.update(|cx| code_actions.detach_and_log_err(cx));
5636                        } else {
5637                            code_actions.await?;
5638                        }
5639                    }
5640                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
5641                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
5642                            log::info!(
5643                                "{}: saving buffer {} ({:?})",
5644                                guest_username,
5645                                buffer.remote_id(),
5646                                buffer.file().unwrap().full_path(cx)
5647                            );
5648                            (buffer.version(), buffer.save(cx))
5649                        });
5650                        let save = cx.background().spawn(async move {
5651                            let (saved_version, _, _) = save
5652                                .await
5653                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
5654                            assert!(saved_version.observed_all(&requested_version));
5655                            Ok::<_, anyhow::Error>(())
5656                        });
5657                        if rng.lock().gen_bool(0.3) {
5658                            log::info!("{}: detaching save request", guest_username);
5659                            cx.update(|cx| save.detach_and_log_err(cx));
5660                        } else {
5661                            save.await?;
5662                        }
5663                    }
5664                    40..=44 => {
5665                        let prepare_rename = project.update(cx, |project, cx| {
5666                            log::info!(
5667                                "{}: preparing rename for buffer {} ({:?})",
5668                                guest_username,
5669                                buffer.read(cx).remote_id(),
5670                                buffer.read(cx).file().unwrap().full_path(cx)
5671                            );
5672                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5673                            project.prepare_rename(buffer, offset, cx)
5674                        });
5675                        let prepare_rename = cx.background().spawn(async move {
5676                            prepare_rename
5677                                .await
5678                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
5679                        });
5680                        if rng.lock().gen_bool(0.3) {
5681                            log::info!("{}: detaching prepare rename request", guest_username);
5682                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
5683                        } else {
5684                            prepare_rename.await?;
5685                        }
5686                    }
5687                    45..=49 => {
5688                        let definitions = project.update(cx, |project, cx| {
5689                            log::info!(
5690                                "{}: requesting definitions for buffer {} ({:?})",
5691                                guest_username,
5692                                buffer.read(cx).remote_id(),
5693                                buffer.read(cx).file().unwrap().full_path(cx)
5694                            );
5695                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5696                            project.definition(&buffer, offset, cx)
5697                        });
5698                        let definitions = cx.background().spawn(async move {
5699                            definitions
5700                                .await
5701                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
5702                        });
5703                        if rng.lock().gen_bool(0.3) {
5704                            log::info!("{}: detaching definitions request", guest_username);
5705                            cx.update(|cx| definitions.detach_and_log_err(cx));
5706                        } else {
5707                            client.buffers.extend(
5708                                definitions.await?.into_iter().map(|loc| loc.target.buffer),
5709                            );
5710                        }
5711                    }
5712                    50..=54 => {
5713                        let highlights = project.update(cx, |project, cx| {
5714                            log::info!(
5715                                "{}: requesting highlights for buffer {} ({:?})",
5716                                guest_username,
5717                                buffer.read(cx).remote_id(),
5718                                buffer.read(cx).file().unwrap().full_path(cx)
5719                            );
5720                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
5721                            project.document_highlights(&buffer, offset, cx)
5722                        });
5723                        let highlights = cx.background().spawn(async move {
5724                            highlights
5725                                .await
5726                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
5727                        });
5728                        if rng.lock().gen_bool(0.3) {
5729                            log::info!("{}: detaching highlights request", guest_username);
5730                            cx.update(|cx| highlights.detach_and_log_err(cx));
5731                        } else {
5732                            highlights.await?;
5733                        }
5734                    }
5735                    55..=59 => {
5736                        let search = project.update(cx, |project, cx| {
5737                            let query = rng.lock().gen_range('a'..='z');
5738                            log::info!("{}: project-wide search {:?}", guest_username, query);
5739                            project.search(SearchQuery::text(query, false, false), cx)
5740                        });
5741                        let search = cx.background().spawn(async move {
5742                            search
5743                                .await
5744                                .map_err(|err| anyhow!("search request failed: {:?}", err))
5745                        });
5746                        if rng.lock().gen_bool(0.3) {
5747                            log::info!("{}: detaching search request", guest_username);
5748                            cx.update(|cx| search.detach_and_log_err(cx));
5749                        } else {
5750                            client.buffers.extend(search.await?.into_keys());
5751                        }
5752                    }
5753                    60..=69 => {
5754                        let worktree = project
5755                            .read_with(cx, |project, cx| {
5756                                project
5757                                    .worktrees(cx)
5758                                    .filter(|worktree| {
5759                                        let worktree = worktree.read(cx);
5760                                        worktree.is_visible()
5761                                            && worktree.entries(false).any(|e| e.is_file())
5762                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
5763                                    })
5764                                    .choose(&mut *rng.lock())
5765                            })
5766                            .unwrap();
5767                        let (worktree_id, worktree_root_name) = worktree
5768                            .read_with(cx, |worktree, _| {
5769                                (worktree.id(), worktree.root_name().to_string())
5770                            });
5771
5772                        let mut new_name = String::new();
5773                        for _ in 0..10 {
5774                            let letter = rng.lock().gen_range('a'..='z');
5775                            new_name.push(letter);
5776                        }
5777                        let mut new_path = PathBuf::new();
5778                        new_path.push(new_name);
5779                        new_path.set_extension("rs");
5780                        log::info!(
5781                            "{}: creating {:?} in worktree {} ({})",
5782                            guest_username,
5783                            new_path,
5784                            worktree_id,
5785                            worktree_root_name,
5786                        );
5787                        project
5788                            .update(cx, |project, cx| {
5789                                project.create_entry((worktree_id, new_path), false, cx)
5790                            })
5791                            .unwrap()
5792                            .await?;
5793                    }
5794                    _ => {
5795                        buffer.update(cx, |buffer, cx| {
5796                            log::info!(
5797                                "{}: updating buffer {} ({:?})",
5798                                guest_username,
5799                                buffer.remote_id(),
5800                                buffer.file().unwrap().full_path(cx)
5801                            );
5802                            if rng.lock().gen_bool(0.7) {
5803                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
5804                            } else {
5805                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
5806                            }
5807                        });
5808                    }
5809                }
5810                cx.background().simulate_random_delay().await;
5811            }
5812            Ok(())
5813        }
5814
5815        let result = simulate_guest_internal(
5816            &mut self,
5817            &guest_username,
5818            project.clone(),
5819            op_start_signal,
5820            rng,
5821            &mut cx,
5822        )
5823        .await;
5824        log::info!("{}: done", guest_username);
5825
5826        (self, project, cx, result.err())
5827    }
5828}
5829
5830impl Drop for TestClient {
5831    fn drop(&mut self) {
5832        self.client.tear_down();
5833    }
5834}
5835
5836impl Executor for Arc<gpui::executor::Background> {
5837    type Sleep = gpui::executor::Timer;
5838
5839    fn spawn_detached<F: 'static + Send + Future<Output = ()>>(&self, future: F) {
5840        self.spawn(future).detach();
5841    }
5842
5843    fn sleep(&self, duration: Duration) -> Self::Sleep {
5844        self.as_ref().timer(duration)
5845    }
5846}
5847
5848fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
5849    channel
5850        .messages()
5851        .cursor::<()>()
5852        .map(|m| {
5853            (
5854                m.sender.github_login.clone(),
5855                m.body.clone(),
5856                m.is_pending(),
5857            )
5858        })
5859        .collect()
5860}
5861
5862#[derive(Debug, Eq, PartialEq)]
5863struct RoomParticipants {
5864    remote: Vec<String>,
5865    pending: Vec<String>,
5866}
5867
5868async fn room_participants(
5869    room: &ModelHandle<Room>,
5870    client: &TestClient,
5871    cx: &mut TestAppContext,
5872) -> RoomParticipants {
5873    let remote_users = room.update(cx, |room, cx| {
5874        room.remote_participants()
5875            .values()
5876            .map(|participant| {
5877                client
5878                    .user_store
5879                    .update(cx, |users, cx| users.get_user(participant.user_id, cx))
5880            })
5881            .collect::<Vec<_>>()
5882    });
5883    let remote_users = futures::future::try_join_all(remote_users).await.unwrap();
5884    let pending_users = room.read_with(cx, |room, _| {
5885        room.pending_users().iter().cloned().collect::<Vec<_>>()
5886    });
5887
5888    RoomParticipants {
5889        remote: remote_users
5890            .into_iter()
5891            .map(|user| user.github_login.clone())
5892            .collect(),
5893        pending: pending_users
5894            .into_iter()
5895            .map(|user| user.github_login.clone())
5896            .collect(),
5897    }
5898}