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