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