integration_tests.rs

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