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_b = 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_b.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    // Drop client B's connection and ensure client A and client C observe client B leaving the project.
2036    client_b.disconnect(&cx_b.to_async()).unwrap();
2037    deterministic.run_until_parked();
2038    project_a.read_with(cx_a, |project, _| {
2039        assert_eq!(project.collaborators().len(), 1);
2040    });
2041    project_b.read_with(cx_b, |project, _| {
2042        assert!(project.is_read_only());
2043    });
2044    project_c.read_with(cx_c, |project, _| {
2045        assert_eq!(project.collaborators().len(), 1);
2046    });
2047
2048    // Client B can't join the project, unless they re-join the room.
2049    cx_b.spawn(|cx| {
2050        Project::remote(
2051            project_id,
2052            client_b.client.clone(),
2053            client_b.user_store.clone(),
2054            client_b.project_store.clone(),
2055            client_b.language_registry.clone(),
2056            FakeFs::new(cx.background()),
2057            cx,
2058        )
2059    })
2060    .await
2061    .unwrap_err();
2062
2063    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
2064    client_c.wait_for_current_user(cx_c).await;
2065    server.disconnect_client(client_c.current_user_id(cx_c));
2066    cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
2067    deterministic.run_until_parked();
2068    project_a.read_with(cx_a, |project, _| {
2069        assert_eq!(project.collaborators().len(), 0);
2070    });
2071    project_b.read_with(cx_b, |project, _| {
2072        assert!(project.is_read_only());
2073    });
2074    project_c.read_with(cx_c, |project, _| {
2075        assert!(project.is_read_only());
2076    });
2077}
2078
2079#[gpui::test(iterations = 10)]
2080async fn test_collaborating_with_diagnostics(
2081    deterministic: Arc<Deterministic>,
2082    cx_a: &mut TestAppContext,
2083    cx_b: &mut TestAppContext,
2084    cx_c: &mut TestAppContext,
2085) {
2086    deterministic.forbid_parking();
2087    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2088    let client_a = server.create_client(cx_a, "user_a").await;
2089    let client_b = server.create_client(cx_b, "user_b").await;
2090    let client_c = server.create_client(cx_c, "user_c").await;
2091    server
2092        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2093        .await;
2094    let active_call_a = cx_a.read(ActiveCall::global);
2095
2096    // Set up a fake language server.
2097    let mut language = Language::new(
2098        LanguageConfig {
2099            name: "Rust".into(),
2100            path_suffixes: vec!["rs".to_string()],
2101            ..Default::default()
2102        },
2103        Some(tree_sitter_rust::language()),
2104    );
2105    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2106    client_a.language_registry.add(Arc::new(language));
2107
2108    // Share a project as client A
2109    client_a
2110        .fs
2111        .insert_tree(
2112            "/a",
2113            json!({
2114                "a.rs": "let one = two",
2115                "other.rs": "",
2116            }),
2117        )
2118        .await;
2119    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2120    let project_id = active_call_a
2121        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2122        .await
2123        .unwrap();
2124
2125    // Cause the language server to start.
2126    let _buffer = cx_a
2127        .background()
2128        .spawn(project_a.update(cx_a, |project, cx| {
2129            project.open_buffer(
2130                ProjectPath {
2131                    worktree_id,
2132                    path: Path::new("other.rs").into(),
2133                },
2134                cx,
2135            )
2136        }))
2137        .await
2138        .unwrap();
2139
2140    // Join the worktree as client B.
2141    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2142
2143    // Simulate a language server reporting errors for a file.
2144    let mut fake_language_server = fake_language_servers.next().await.unwrap();
2145    fake_language_server
2146        .receive_notification::<lsp::notification::DidOpenTextDocument>()
2147        .await;
2148    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2149        lsp::PublishDiagnosticsParams {
2150            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2151            version: None,
2152            diagnostics: vec![lsp::Diagnostic {
2153                severity: Some(lsp::DiagnosticSeverity::ERROR),
2154                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2155                message: "message 1".to_string(),
2156                ..Default::default()
2157            }],
2158        },
2159    );
2160
2161    // Wait for server to see the diagnostics update.
2162    deterministic.run_until_parked();
2163    {
2164        let store = server.store.lock().await;
2165        let project = store.project(ProjectId::from_proto(project_id)).unwrap();
2166        let worktree = project.worktrees.get(&worktree_id.to_proto()).unwrap();
2167        assert!(!worktree.diagnostic_summaries.is_empty());
2168    }
2169
2170    // Ensure client B observes the new diagnostics.
2171    project_b.read_with(cx_b, |project, cx| {
2172        assert_eq!(
2173            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2174            &[(
2175                ProjectPath {
2176                    worktree_id,
2177                    path: Arc::from(Path::new("a.rs")),
2178                },
2179                DiagnosticSummary {
2180                    error_count: 1,
2181                    warning_count: 0,
2182                    ..Default::default()
2183                },
2184            )]
2185        )
2186    });
2187
2188    // Join project as client C and observe the diagnostics.
2189    let project_c = client_c.build_remote_project(project_id, cx_c).await;
2190    deterministic.run_until_parked();
2191    project_c.read_with(cx_c, |project, cx| {
2192        assert_eq!(
2193            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2194            &[(
2195                ProjectPath {
2196                    worktree_id,
2197                    path: Arc::from(Path::new("a.rs")),
2198                },
2199                DiagnosticSummary {
2200                    error_count: 1,
2201                    warning_count: 0,
2202                    ..Default::default()
2203                },
2204            )]
2205        )
2206    });
2207
2208    // Simulate a language server reporting more errors for a file.
2209    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2210        lsp::PublishDiagnosticsParams {
2211            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2212            version: None,
2213            diagnostics: vec![
2214                lsp::Diagnostic {
2215                    severity: Some(lsp::DiagnosticSeverity::ERROR),
2216                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
2217                    message: "message 1".to_string(),
2218                    ..Default::default()
2219                },
2220                lsp::Diagnostic {
2221                    severity: Some(lsp::DiagnosticSeverity::WARNING),
2222                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
2223                    message: "message 2".to_string(),
2224                    ..Default::default()
2225                },
2226            ],
2227        },
2228    );
2229
2230    // Clients B and C get the updated summaries
2231    deterministic.run_until_parked();
2232    project_b.read_with(cx_b, |project, cx| {
2233        assert_eq!(
2234            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2235            [(
2236                ProjectPath {
2237                    worktree_id,
2238                    path: Arc::from(Path::new("a.rs")),
2239                },
2240                DiagnosticSummary {
2241                    error_count: 1,
2242                    warning_count: 1,
2243                    ..Default::default()
2244                },
2245            )]
2246        );
2247    });
2248    project_c.read_with(cx_c, |project, cx| {
2249        assert_eq!(
2250            project.diagnostic_summaries(cx).collect::<Vec<_>>(),
2251            [(
2252                ProjectPath {
2253                    worktree_id,
2254                    path: Arc::from(Path::new("a.rs")),
2255                },
2256                DiagnosticSummary {
2257                    error_count: 1,
2258                    warning_count: 1,
2259                    ..Default::default()
2260                },
2261            )]
2262        );
2263    });
2264
2265    // Open the file with the errors on client B. They should be present.
2266    let buffer_b = cx_b
2267        .background()
2268        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2269        .await
2270        .unwrap();
2271
2272    buffer_b.read_with(cx_b, |buffer, _| {
2273        assert_eq!(
2274            buffer
2275                .snapshot()
2276                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
2277                .collect::<Vec<_>>(),
2278            &[
2279                DiagnosticEntry {
2280                    range: Point::new(0, 4)..Point::new(0, 7),
2281                    diagnostic: Diagnostic {
2282                        group_id: 1,
2283                        message: "message 1".to_string(),
2284                        severity: lsp::DiagnosticSeverity::ERROR,
2285                        is_primary: true,
2286                        ..Default::default()
2287                    }
2288                },
2289                DiagnosticEntry {
2290                    range: Point::new(0, 10)..Point::new(0, 13),
2291                    diagnostic: Diagnostic {
2292                        group_id: 2,
2293                        severity: lsp::DiagnosticSeverity::WARNING,
2294                        message: "message 2".to_string(),
2295                        is_primary: true,
2296                        ..Default::default()
2297                    }
2298                }
2299            ]
2300        );
2301    });
2302
2303    // Simulate a language server reporting no errors for a file.
2304    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
2305        lsp::PublishDiagnosticsParams {
2306            uri: lsp::Url::from_file_path("/a/a.rs").unwrap(),
2307            version: None,
2308            diagnostics: vec![],
2309        },
2310    );
2311    deterministic.run_until_parked();
2312    project_a.read_with(cx_a, |project, cx| {
2313        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
2314    });
2315    project_b.read_with(cx_b, |project, cx| {
2316        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
2317    });
2318    project_c.read_with(cx_c, |project, cx| {
2319        assert_eq!(project.diagnostic_summaries(cx).collect::<Vec<_>>(), [])
2320    });
2321}
2322
2323#[gpui::test(iterations = 10)]
2324async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2325    cx_a.foreground().forbid_parking();
2326    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2327    let client_a = server.create_client(cx_a, "user_a").await;
2328    let client_b = server.create_client(cx_b, "user_b").await;
2329    server
2330        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2331        .await;
2332    let active_call_a = cx_a.read(ActiveCall::global);
2333
2334    // Set up a fake language server.
2335    let mut language = Language::new(
2336        LanguageConfig {
2337            name: "Rust".into(),
2338            path_suffixes: vec!["rs".to_string()],
2339            ..Default::default()
2340        },
2341        Some(tree_sitter_rust::language()),
2342    );
2343    let mut fake_language_servers = language
2344        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
2345            capabilities: lsp::ServerCapabilities {
2346                completion_provider: Some(lsp::CompletionOptions {
2347                    trigger_characters: Some(vec![".".to_string()]),
2348                    ..Default::default()
2349                }),
2350                ..Default::default()
2351            },
2352            ..Default::default()
2353        }))
2354        .await;
2355    client_a.language_registry.add(Arc::new(language));
2356
2357    client_a
2358        .fs
2359        .insert_tree(
2360            "/a",
2361            json!({
2362                "main.rs": "fn main() { a }",
2363                "other.rs": "",
2364            }),
2365        )
2366        .await;
2367    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2368    let project_id = active_call_a
2369        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2370        .await
2371        .unwrap();
2372    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2373
2374    // Open a file in an editor as the guest.
2375    let buffer_b = project_b
2376        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
2377        .await
2378        .unwrap();
2379    let (_, window_b) = cx_b.add_window(|_| EmptyView);
2380    let editor_b = cx_b.add_view(&window_b, |cx| {
2381        Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
2382    });
2383
2384    let fake_language_server = fake_language_servers.next().await.unwrap();
2385    buffer_b
2386        .condition(cx_b, |buffer, _| !buffer.completion_triggers().is_empty())
2387        .await;
2388
2389    // Type a completion trigger character as the guest.
2390    editor_b.update(cx_b, |editor, cx| {
2391        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
2392        editor.handle_input(".", cx);
2393        cx.focus(&editor_b);
2394    });
2395
2396    // Receive a completion request as the host's language server.
2397    // Return some completions from the host's language server.
2398    cx_a.foreground().start_waiting();
2399    fake_language_server
2400        .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
2401            assert_eq!(
2402                params.text_document_position.text_document.uri,
2403                lsp::Url::from_file_path("/a/main.rs").unwrap(),
2404            );
2405            assert_eq!(
2406                params.text_document_position.position,
2407                lsp::Position::new(0, 14),
2408            );
2409
2410            Ok(Some(lsp::CompletionResponse::Array(vec![
2411                lsp::CompletionItem {
2412                    label: "first_method(…)".into(),
2413                    detail: Some("fn(&mut self, B) -> C".into()),
2414                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
2415                        new_text: "first_method($1)".to_string(),
2416                        range: lsp::Range::new(
2417                            lsp::Position::new(0, 14),
2418                            lsp::Position::new(0, 14),
2419                        ),
2420                    })),
2421                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
2422                    ..Default::default()
2423                },
2424                lsp::CompletionItem {
2425                    label: "second_method(…)".into(),
2426                    detail: Some("fn(&mut self, C) -> D<E>".into()),
2427                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
2428                        new_text: "second_method()".to_string(),
2429                        range: lsp::Range::new(
2430                            lsp::Position::new(0, 14),
2431                            lsp::Position::new(0, 14),
2432                        ),
2433                    })),
2434                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
2435                    ..Default::default()
2436                },
2437            ])))
2438        })
2439        .next()
2440        .await
2441        .unwrap();
2442    cx_a.foreground().finish_waiting();
2443
2444    // Open the buffer on the host.
2445    let buffer_a = project_a
2446        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
2447        .await
2448        .unwrap();
2449    buffer_a
2450        .condition(cx_a, |buffer, _| buffer.text() == "fn main() { a. }")
2451        .await;
2452
2453    // Confirm a completion on the guest.
2454    editor_b
2455        .condition(cx_b, |editor, _| editor.context_menu_visible())
2456        .await;
2457    editor_b.update(cx_b, |editor, cx| {
2458        editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
2459        assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
2460    });
2461
2462    // Return a resolved completion from the host's language server.
2463    // The resolved completion has an additional text edit.
2464    fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
2465        |params, _| async move {
2466            assert_eq!(params.label, "first_method(…)");
2467            Ok(lsp::CompletionItem {
2468                label: "first_method(…)".into(),
2469                detail: Some("fn(&mut self, B) -> C".into()),
2470                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
2471                    new_text: "first_method($1)".to_string(),
2472                    range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
2473                })),
2474                additional_text_edits: Some(vec![lsp::TextEdit {
2475                    new_text: "use d::SomeTrait;\n".to_string(),
2476                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
2477                }]),
2478                insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
2479                ..Default::default()
2480            })
2481        },
2482    );
2483
2484    // The additional edit is applied.
2485    buffer_a
2486        .condition(cx_a, |buffer, _| {
2487            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
2488        })
2489        .await;
2490    buffer_b
2491        .condition(cx_b, |buffer, _| {
2492            buffer.text() == "use d::SomeTrait;\nfn main() { a.first_method() }"
2493        })
2494        .await;
2495}
2496
2497#[gpui::test(iterations = 10)]
2498async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2499    cx_a.foreground().forbid_parking();
2500    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2501    let client_a = server.create_client(cx_a, "user_a").await;
2502    let client_b = server.create_client(cx_b, "user_b").await;
2503    server
2504        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2505        .await;
2506    let active_call_a = cx_a.read(ActiveCall::global);
2507
2508    client_a
2509        .fs
2510        .insert_tree("/a", json!({ "a.rs": "let one = 1;" }))
2511        .await;
2512    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
2513    let buffer_a = project_a
2514        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
2515        .await
2516        .unwrap();
2517    let project_id = active_call_a
2518        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2519        .await
2520        .unwrap();
2521
2522    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2523
2524    let buffer_b = cx_b
2525        .background()
2526        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2527        .await
2528        .unwrap();
2529    buffer_b.update(cx_b, |buffer, cx| {
2530        buffer.edit([(4..7, "six")], None, cx);
2531        buffer.edit([(10..11, "6")], None, cx);
2532        assert_eq!(buffer.text(), "let six = 6;");
2533        assert!(buffer.is_dirty());
2534        assert!(!buffer.has_conflict());
2535    });
2536    buffer_a
2537        .condition(cx_a, |buffer, _| buffer.text() == "let six = 6;")
2538        .await;
2539
2540    client_a
2541        .fs
2542        .save(
2543            "/a/a.rs".as_ref(),
2544            &Rope::from("let seven = 7;"),
2545            LineEnding::Unix,
2546        )
2547        .await
2548        .unwrap();
2549    buffer_a
2550        .condition(cx_a, |buffer, _| buffer.has_conflict())
2551        .await;
2552    buffer_b
2553        .condition(cx_b, |buffer, _| buffer.has_conflict())
2554        .await;
2555
2556    project_b
2557        .update(cx_b, |project, cx| {
2558            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
2559        })
2560        .await
2561        .unwrap();
2562    buffer_a.read_with(cx_a, |buffer, _| {
2563        assert_eq!(buffer.text(), "let seven = 7;");
2564        assert!(!buffer.is_dirty());
2565        assert!(!buffer.has_conflict());
2566    });
2567    buffer_b.read_with(cx_b, |buffer, _| {
2568        assert_eq!(buffer.text(), "let seven = 7;");
2569        assert!(!buffer.is_dirty());
2570        assert!(!buffer.has_conflict());
2571    });
2572
2573    buffer_a.update(cx_a, |buffer, cx| {
2574        // Undoing on the host is a no-op when the reload was initiated by the guest.
2575        buffer.undo(cx);
2576        assert_eq!(buffer.text(), "let seven = 7;");
2577        assert!(!buffer.is_dirty());
2578        assert!(!buffer.has_conflict());
2579    });
2580    buffer_b.update(cx_b, |buffer, cx| {
2581        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
2582        buffer.undo(cx);
2583        assert_eq!(buffer.text(), "let six = 6;");
2584        assert!(buffer.is_dirty());
2585        assert!(!buffer.has_conflict());
2586    });
2587}
2588
2589#[gpui::test(iterations = 10)]
2590async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2591    use project::FormatTrigger;
2592
2593    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2594    let client_a = server.create_client(cx_a, "user_a").await;
2595    let client_b = server.create_client(cx_b, "user_b").await;
2596    server
2597        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2598        .await;
2599    let active_call_a = cx_a.read(ActiveCall::global);
2600
2601    // Set up a fake language server.
2602    let mut language = Language::new(
2603        LanguageConfig {
2604            name: "Rust".into(),
2605            path_suffixes: vec!["rs".to_string()],
2606            ..Default::default()
2607        },
2608        Some(tree_sitter_rust::language()),
2609    );
2610    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2611    client_a.language_registry.add(Arc::new(language));
2612
2613    // Here we insert a fake tree with a directory that exists on disk. This is needed
2614    // because later we'll invoke a command, which requires passing a working directory
2615    // that points to a valid location on disk.
2616    let directory = env::current_dir().unwrap();
2617    client_a
2618        .fs
2619        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
2620        .await;
2621    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
2622    let project_id = active_call_a
2623        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2624        .await
2625        .unwrap();
2626    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2627
2628    let buffer_b = cx_b
2629        .background()
2630        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2631        .await
2632        .unwrap();
2633
2634    let fake_language_server = fake_language_servers.next().await.unwrap();
2635    fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
2636        Ok(Some(vec![
2637            lsp::TextEdit {
2638                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
2639                new_text: "h".to_string(),
2640            },
2641            lsp::TextEdit {
2642                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
2643                new_text: "y".to_string(),
2644            },
2645        ]))
2646    });
2647
2648    project_b
2649        .update(cx_b, |project, cx| {
2650            project.format(
2651                HashSet::from_iter([buffer_b.clone()]),
2652                true,
2653                FormatTrigger::Save,
2654                cx,
2655            )
2656        })
2657        .await
2658        .unwrap();
2659    assert_eq!(
2660        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2661        "let honey = \"two\""
2662    );
2663
2664    // Ensure buffer can be formatted using an external command. Notice how the
2665    // host's configuration is honored as opposed to using the guest's settings.
2666    cx_a.update(|cx| {
2667        cx.update_global(|settings: &mut Settings, _| {
2668            settings.editor_defaults.formatter = Some(Formatter::External {
2669                command: "awk".to_string(),
2670                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
2671            });
2672        });
2673    });
2674    project_b
2675        .update(cx_b, |project, cx| {
2676            project.format(
2677                HashSet::from_iter([buffer_b.clone()]),
2678                true,
2679                FormatTrigger::Save,
2680                cx,
2681            )
2682        })
2683        .await
2684        .unwrap();
2685    assert_eq!(
2686        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
2687        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
2688    );
2689}
2690
2691#[gpui::test(iterations = 10)]
2692async fn test_definition(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2693    cx_a.foreground().forbid_parking();
2694    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2695    let client_a = server.create_client(cx_a, "user_a").await;
2696    let client_b = server.create_client(cx_b, "user_b").await;
2697    server
2698        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2699        .await;
2700    let active_call_a = cx_a.read(ActiveCall::global);
2701
2702    // Set up a fake language server.
2703    let mut language = Language::new(
2704        LanguageConfig {
2705            name: "Rust".into(),
2706            path_suffixes: vec!["rs".to_string()],
2707            ..Default::default()
2708        },
2709        Some(tree_sitter_rust::language()),
2710    );
2711    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2712    client_a.language_registry.add(Arc::new(language));
2713
2714    client_a
2715        .fs
2716        .insert_tree(
2717            "/root",
2718            json!({
2719                "dir-1": {
2720                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
2721                },
2722                "dir-2": {
2723                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
2724                    "c.rs": "type T2 = usize;",
2725                }
2726            }),
2727        )
2728        .await;
2729    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2730    let project_id = active_call_a
2731        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2732        .await
2733        .unwrap();
2734    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2735
2736    // Open the file on client B.
2737    let buffer_b = cx_b
2738        .background()
2739        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
2740        .await
2741        .unwrap();
2742
2743    // Request the definition of a symbol as the guest.
2744    let fake_language_server = fake_language_servers.next().await.unwrap();
2745    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2746        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2747            lsp::Location::new(
2748                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2749                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
2750            ),
2751        )))
2752    });
2753
2754    let definitions_1 = project_b
2755        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
2756        .await
2757        .unwrap();
2758    cx_b.read(|cx| {
2759        assert_eq!(definitions_1.len(), 1);
2760        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2761        let target_buffer = definitions_1[0].target.buffer.read(cx);
2762        assert_eq!(
2763            target_buffer.text(),
2764            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
2765        );
2766        assert_eq!(
2767            definitions_1[0].target.range.to_point(target_buffer),
2768            Point::new(0, 6)..Point::new(0, 9)
2769        );
2770    });
2771
2772    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
2773    // the previous call to `definition`.
2774    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
2775        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2776            lsp::Location::new(
2777                lsp::Url::from_file_path("/root/dir-2/b.rs").unwrap(),
2778                lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
2779            ),
2780        )))
2781    });
2782
2783    let definitions_2 = project_b
2784        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
2785        .await
2786        .unwrap();
2787    cx_b.read(|cx| {
2788        assert_eq!(definitions_2.len(), 1);
2789        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2790        let target_buffer = definitions_2[0].target.buffer.read(cx);
2791        assert_eq!(
2792            target_buffer.text(),
2793            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
2794        );
2795        assert_eq!(
2796            definitions_2[0].target.range.to_point(target_buffer),
2797            Point::new(1, 6)..Point::new(1, 11)
2798        );
2799    });
2800    assert_eq!(
2801        definitions_1[0].target.buffer,
2802        definitions_2[0].target.buffer
2803    );
2804
2805    fake_language_server.handle_request::<lsp::request::GotoTypeDefinition, _, _>(
2806        |req, _| async move {
2807            assert_eq!(
2808                req.text_document_position_params.position,
2809                lsp::Position::new(0, 7)
2810            );
2811            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
2812                lsp::Location::new(
2813                    lsp::Url::from_file_path("/root/dir-2/c.rs").unwrap(),
2814                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
2815                ),
2816            )))
2817        },
2818    );
2819
2820    let type_definitions = project_b
2821        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
2822        .await
2823        .unwrap();
2824    cx_b.read(|cx| {
2825        assert_eq!(type_definitions.len(), 1);
2826        let target_buffer = type_definitions[0].target.buffer.read(cx);
2827        assert_eq!(target_buffer.text(), "type T2 = usize;");
2828        assert_eq!(
2829            type_definitions[0].target.range.to_point(target_buffer),
2830            Point::new(0, 5)..Point::new(0, 7)
2831        );
2832    });
2833}
2834
2835#[gpui::test(iterations = 10)]
2836async fn test_references(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2837    cx_a.foreground().forbid_parking();
2838    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2839    let client_a = server.create_client(cx_a, "user_a").await;
2840    let client_b = server.create_client(cx_b, "user_b").await;
2841    server
2842        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2843        .await;
2844    let active_call_a = cx_a.read(ActiveCall::global);
2845
2846    // Set up a fake language server.
2847    let mut language = Language::new(
2848        LanguageConfig {
2849            name: "Rust".into(),
2850            path_suffixes: vec!["rs".to_string()],
2851            ..Default::default()
2852        },
2853        Some(tree_sitter_rust::language()),
2854    );
2855    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
2856    client_a.language_registry.add(Arc::new(language));
2857
2858    client_a
2859        .fs
2860        .insert_tree(
2861            "/root",
2862            json!({
2863                "dir-1": {
2864                    "one.rs": "const ONE: usize = 1;",
2865                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
2866                },
2867                "dir-2": {
2868                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
2869                }
2870            }),
2871        )
2872        .await;
2873    let (project_a, worktree_id) = client_a.build_local_project("/root/dir-1", cx_a).await;
2874    let project_id = active_call_a
2875        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2876        .await
2877        .unwrap();
2878    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2879
2880    // Open the file on client B.
2881    let buffer_b = cx_b
2882        .background()
2883        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
2884        .await
2885        .unwrap();
2886
2887    // Request references to a symbol as the guest.
2888    let fake_language_server = fake_language_servers.next().await.unwrap();
2889    fake_language_server.handle_request::<lsp::request::References, _, _>(|params, _| async move {
2890        assert_eq!(
2891            params.text_document_position.text_document.uri.as_str(),
2892            "file:///root/dir-1/one.rs"
2893        );
2894        Ok(Some(vec![
2895            lsp::Location {
2896                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2897                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
2898            },
2899            lsp::Location {
2900                uri: lsp::Url::from_file_path("/root/dir-1/two.rs").unwrap(),
2901                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
2902            },
2903            lsp::Location {
2904                uri: lsp::Url::from_file_path("/root/dir-2/three.rs").unwrap(),
2905                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
2906            },
2907        ]))
2908    });
2909
2910    let references = project_b
2911        .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
2912        .await
2913        .unwrap();
2914    cx_b.read(|cx| {
2915        assert_eq!(references.len(), 3);
2916        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
2917
2918        let two_buffer = references[0].buffer.read(cx);
2919        let three_buffer = references[2].buffer.read(cx);
2920        assert_eq!(
2921            two_buffer.file().unwrap().path().as_ref(),
2922            Path::new("two.rs")
2923        );
2924        assert_eq!(references[1].buffer, references[0].buffer);
2925        assert_eq!(
2926            three_buffer.file().unwrap().full_path(cx),
2927            Path::new("three.rs")
2928        );
2929
2930        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
2931        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
2932        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
2933    });
2934}
2935
2936#[gpui::test(iterations = 10)]
2937async fn test_project_search(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
2938    cx_a.foreground().forbid_parking();
2939    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
2940    let client_a = server.create_client(cx_a, "user_a").await;
2941    let client_b = server.create_client(cx_b, "user_b").await;
2942    server
2943        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2944        .await;
2945    let active_call_a = cx_a.read(ActiveCall::global);
2946
2947    client_a
2948        .fs
2949        .insert_tree(
2950            "/root",
2951            json!({
2952                "dir-1": {
2953                    "a": "hello world",
2954                    "b": "goodnight moon",
2955                    "c": "a world of goo",
2956                    "d": "world champion of clown world",
2957                },
2958                "dir-2": {
2959                    "e": "disney world is fun",
2960                }
2961            }),
2962        )
2963        .await;
2964    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
2965    let (worktree_2, _) = project_a
2966        .update(cx_a, |p, cx| {
2967            p.find_or_create_local_worktree("/root/dir-2", true, cx)
2968        })
2969        .await
2970        .unwrap();
2971    worktree_2
2972        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
2973        .await;
2974    let project_id = active_call_a
2975        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2976        .await
2977        .unwrap();
2978
2979    let project_b = client_b.build_remote_project(project_id, cx_b).await;
2980
2981    // Perform a search as the guest.
2982    let results = project_b
2983        .update(cx_b, |project, cx| {
2984            project.search(SearchQuery::text("world", false, false), cx)
2985        })
2986        .await
2987        .unwrap();
2988
2989    let mut ranges_by_path = results
2990        .into_iter()
2991        .map(|(buffer, ranges)| {
2992            buffer.read_with(cx_b, |buffer, cx| {
2993                let path = buffer.file().unwrap().full_path(cx);
2994                let offset_ranges = ranges
2995                    .into_iter()
2996                    .map(|range| range.to_offset(buffer))
2997                    .collect::<Vec<_>>();
2998                (path, offset_ranges)
2999            })
3000        })
3001        .collect::<Vec<_>>();
3002    ranges_by_path.sort_by_key(|(path, _)| path.clone());
3003
3004    assert_eq!(
3005        ranges_by_path,
3006        &[
3007            (PathBuf::from("dir-1/a"), vec![6..11]),
3008            (PathBuf::from("dir-1/c"), vec![2..7]),
3009            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
3010            (PathBuf::from("dir-2/e"), vec![7..12]),
3011        ]
3012    );
3013}
3014
3015#[gpui::test(iterations = 10)]
3016async fn test_document_highlights(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3017    cx_a.foreground().forbid_parking();
3018    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3019    let client_a = server.create_client(cx_a, "user_a").await;
3020    let client_b = server.create_client(cx_b, "user_b").await;
3021    server
3022        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3023        .await;
3024    let active_call_a = cx_a.read(ActiveCall::global);
3025
3026    client_a
3027        .fs
3028        .insert_tree(
3029            "/root-1",
3030            json!({
3031                "main.rs": "fn double(number: i32) -> i32 { number + number }",
3032            }),
3033        )
3034        .await;
3035
3036    // Set up a fake language server.
3037    let mut language = Language::new(
3038        LanguageConfig {
3039            name: "Rust".into(),
3040            path_suffixes: vec!["rs".to_string()],
3041            ..Default::default()
3042        },
3043        Some(tree_sitter_rust::language()),
3044    );
3045    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3046    client_a.language_registry.add(Arc::new(language));
3047
3048    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3049    let project_id = active_call_a
3050        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3051        .await
3052        .unwrap();
3053    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3054
3055    // Open the file on client B.
3056    let buffer_b = cx_b
3057        .background()
3058        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3059        .await
3060        .unwrap();
3061
3062    // Request document highlights as the guest.
3063    let fake_language_server = fake_language_servers.next().await.unwrap();
3064    fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
3065        |params, _| async move {
3066            assert_eq!(
3067                params
3068                    .text_document_position_params
3069                    .text_document
3070                    .uri
3071                    .as_str(),
3072                "file:///root-1/main.rs"
3073            );
3074            assert_eq!(
3075                params.text_document_position_params.position,
3076                lsp::Position::new(0, 34)
3077            );
3078            Ok(Some(vec![
3079                lsp::DocumentHighlight {
3080                    kind: Some(lsp::DocumentHighlightKind::WRITE),
3081                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
3082                },
3083                lsp::DocumentHighlight {
3084                    kind: Some(lsp::DocumentHighlightKind::READ),
3085                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
3086                },
3087                lsp::DocumentHighlight {
3088                    kind: Some(lsp::DocumentHighlightKind::READ),
3089                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
3090                },
3091            ]))
3092        },
3093    );
3094
3095    let highlights = project_b
3096        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
3097        .await
3098        .unwrap();
3099    buffer_b.read_with(cx_b, |buffer, _| {
3100        let snapshot = buffer.snapshot();
3101
3102        let highlights = highlights
3103            .into_iter()
3104            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
3105            .collect::<Vec<_>>();
3106        assert_eq!(
3107            highlights,
3108            &[
3109                (lsp::DocumentHighlightKind::WRITE, 10..16),
3110                (lsp::DocumentHighlightKind::READ, 32..38),
3111                (lsp::DocumentHighlightKind::READ, 41..47)
3112            ]
3113        )
3114    });
3115}
3116
3117#[gpui::test(iterations = 10)]
3118async fn test_lsp_hover(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3119    cx_a.foreground().forbid_parking();
3120    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3121    let client_a = server.create_client(cx_a, "user_a").await;
3122    let client_b = server.create_client(cx_b, "user_b").await;
3123    server
3124        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3125        .await;
3126    let active_call_a = cx_a.read(ActiveCall::global);
3127
3128    client_a
3129        .fs
3130        .insert_tree(
3131            "/root-1",
3132            json!({
3133                "main.rs": "use std::collections::HashMap;",
3134            }),
3135        )
3136        .await;
3137
3138    // Set up a fake language server.
3139    let mut language = Language::new(
3140        LanguageConfig {
3141            name: "Rust".into(),
3142            path_suffixes: vec!["rs".to_string()],
3143            ..Default::default()
3144        },
3145        Some(tree_sitter_rust::language()),
3146    );
3147    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3148    client_a.language_registry.add(Arc::new(language));
3149
3150    let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await;
3151    let project_id = active_call_a
3152        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3153        .await
3154        .unwrap();
3155    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3156
3157    // Open the file as the guest
3158    let buffer_b = cx_b
3159        .background()
3160        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)))
3161        .await
3162        .unwrap();
3163
3164    // Request hover information as the guest.
3165    let fake_language_server = fake_language_servers.next().await.unwrap();
3166    fake_language_server.handle_request::<lsp::request::HoverRequest, _, _>(
3167        |params, _| async move {
3168            assert_eq!(
3169                params
3170                    .text_document_position_params
3171                    .text_document
3172                    .uri
3173                    .as_str(),
3174                "file:///root-1/main.rs"
3175            );
3176            assert_eq!(
3177                params.text_document_position_params.position,
3178                lsp::Position::new(0, 22)
3179            );
3180            Ok(Some(lsp::Hover {
3181                contents: lsp::HoverContents::Array(vec![
3182                    lsp::MarkedString::String("Test hover content.".to_string()),
3183                    lsp::MarkedString::LanguageString(lsp::LanguageString {
3184                        language: "Rust".to_string(),
3185                        value: "let foo = 42;".to_string(),
3186                    }),
3187                ]),
3188                range: Some(lsp::Range::new(
3189                    lsp::Position::new(0, 22),
3190                    lsp::Position::new(0, 29),
3191                )),
3192            }))
3193        },
3194    );
3195
3196    let hover_info = project_b
3197        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
3198        .await
3199        .unwrap()
3200        .unwrap();
3201    buffer_b.read_with(cx_b, |buffer, _| {
3202        let snapshot = buffer.snapshot();
3203        assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29);
3204        assert_eq!(
3205            hover_info.contents,
3206            vec![
3207                project::HoverBlock {
3208                    text: "Test hover content.".to_string(),
3209                    language: None,
3210                },
3211                project::HoverBlock {
3212                    text: "let foo = 42;".to_string(),
3213                    language: Some("Rust".to_string()),
3214                }
3215            ]
3216        );
3217    });
3218}
3219
3220#[gpui::test(iterations = 10)]
3221async fn test_project_symbols(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3222    cx_a.foreground().forbid_parking();
3223    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3224    let client_a = server.create_client(cx_a, "user_a").await;
3225    let client_b = server.create_client(cx_b, "user_b").await;
3226    server
3227        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3228        .await;
3229    let active_call_a = cx_a.read(ActiveCall::global);
3230
3231    // Set up a fake language server.
3232    let mut language = Language::new(
3233        LanguageConfig {
3234            name: "Rust".into(),
3235            path_suffixes: vec!["rs".to_string()],
3236            ..Default::default()
3237        },
3238        Some(tree_sitter_rust::language()),
3239    );
3240    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3241    client_a.language_registry.add(Arc::new(language));
3242
3243    client_a
3244        .fs
3245        .insert_tree(
3246            "/code",
3247            json!({
3248                "crate-1": {
3249                    "one.rs": "const ONE: usize = 1;",
3250                },
3251                "crate-2": {
3252                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
3253                },
3254                "private": {
3255                    "passwords.txt": "the-password",
3256                }
3257            }),
3258        )
3259        .await;
3260    let (project_a, worktree_id) = client_a.build_local_project("/code/crate-1", cx_a).await;
3261    let project_id = active_call_a
3262        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3263        .await
3264        .unwrap();
3265    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3266
3267    // Cause the language server to start.
3268    let _buffer = cx_b
3269        .background()
3270        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "one.rs"), cx)))
3271        .await
3272        .unwrap();
3273
3274    let fake_language_server = fake_language_servers.next().await.unwrap();
3275    fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(|_, _| async move {
3276        #[allow(deprecated)]
3277        Ok(Some(vec![lsp::SymbolInformation {
3278            name: "TWO".into(),
3279            location: lsp::Location {
3280                uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
3281                range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3282            },
3283            kind: lsp::SymbolKind::CONSTANT,
3284            tags: None,
3285            container_name: None,
3286            deprecated: None,
3287        }]))
3288    });
3289
3290    // Request the definition of a symbol as the guest.
3291    let symbols = project_b
3292        .update(cx_b, |p, cx| p.symbols("two", cx))
3293        .await
3294        .unwrap();
3295    assert_eq!(symbols.len(), 1);
3296    assert_eq!(symbols[0].name, "TWO");
3297
3298    // Open one of the returned symbols.
3299    let buffer_b_2 = project_b
3300        .update(cx_b, |project, cx| {
3301            project.open_buffer_for_symbol(&symbols[0], cx)
3302        })
3303        .await
3304        .unwrap();
3305    buffer_b_2.read_with(cx_b, |buffer, _| {
3306        assert_eq!(
3307            buffer.file().unwrap().path().as_ref(),
3308            Path::new("../crate-2/two.rs")
3309        );
3310    });
3311
3312    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
3313    let mut fake_symbol = symbols[0].clone();
3314    fake_symbol.path.path = Path::new("/code/secrets").into();
3315    let error = project_b
3316        .update(cx_b, |project, cx| {
3317            project.open_buffer_for_symbol(&fake_symbol, cx)
3318        })
3319        .await
3320        .unwrap_err();
3321    assert!(error.to_string().contains("invalid symbol signature"));
3322}
3323
3324#[gpui::test(iterations = 10)]
3325async fn test_open_buffer_while_getting_definition_pointing_to_it(
3326    cx_a: &mut TestAppContext,
3327    cx_b: &mut TestAppContext,
3328    mut rng: StdRng,
3329) {
3330    cx_a.foreground().forbid_parking();
3331    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3332    let client_a = server.create_client(cx_a, "user_a").await;
3333    let client_b = server.create_client(cx_b, "user_b").await;
3334    server
3335        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3336        .await;
3337    let active_call_a = cx_a.read(ActiveCall::global);
3338
3339    // Set up a fake language server.
3340    let mut language = Language::new(
3341        LanguageConfig {
3342            name: "Rust".into(),
3343            path_suffixes: vec!["rs".to_string()],
3344            ..Default::default()
3345        },
3346        Some(tree_sitter_rust::language()),
3347    );
3348    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3349    client_a.language_registry.add(Arc::new(language));
3350
3351    client_a
3352        .fs
3353        .insert_tree(
3354            "/root",
3355            json!({
3356                "a.rs": "const ONE: usize = b::TWO;",
3357                "b.rs": "const TWO: usize = 2",
3358            }),
3359        )
3360        .await;
3361    let (project_a, worktree_id) = client_a.build_local_project("/root", cx_a).await;
3362    let project_id = active_call_a
3363        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3364        .await
3365        .unwrap();
3366    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3367
3368    let buffer_b1 = cx_b
3369        .background()
3370        .spawn(project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
3371        .await
3372        .unwrap();
3373
3374    let fake_language_server = fake_language_servers.next().await.unwrap();
3375    fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(|_, _| async move {
3376        Ok(Some(lsp::GotoDefinitionResponse::Scalar(
3377            lsp::Location::new(
3378                lsp::Url::from_file_path("/root/b.rs").unwrap(),
3379                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3380            ),
3381        )))
3382    });
3383
3384    let definitions;
3385    let buffer_b2;
3386    if rng.gen() {
3387        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
3388        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
3389    } else {
3390        buffer_b2 = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.rs"), cx));
3391        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
3392    }
3393
3394    let buffer_b2 = buffer_b2.await.unwrap();
3395    let definitions = definitions.await.unwrap();
3396    assert_eq!(definitions.len(), 1);
3397    assert_eq!(definitions[0].target.buffer, buffer_b2);
3398}
3399
3400#[gpui::test(iterations = 10)]
3401async fn test_collaborating_with_code_actions(
3402    cx_a: &mut TestAppContext,
3403    cx_b: &mut TestAppContext,
3404) {
3405    cx_a.foreground().forbid_parking();
3406    cx_b.update(editor::init);
3407    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3408    let client_a = server.create_client(cx_a, "user_a").await;
3409    let client_b = server.create_client(cx_b, "user_b").await;
3410    server
3411        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3412        .await;
3413    let active_call_a = cx_a.read(ActiveCall::global);
3414
3415    // Set up a fake language server.
3416    let mut language = Language::new(
3417        LanguageConfig {
3418            name: "Rust".into(),
3419            path_suffixes: vec!["rs".to_string()],
3420            ..Default::default()
3421        },
3422        Some(tree_sitter_rust::language()),
3423    );
3424    let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
3425    client_a.language_registry.add(Arc::new(language));
3426
3427    client_a
3428        .fs
3429        .insert_tree(
3430            "/a",
3431            json!({
3432                "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
3433                "other.rs": "pub fn foo() -> usize { 4 }",
3434            }),
3435        )
3436        .await;
3437    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
3438    let project_id = active_call_a
3439        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3440        .await
3441        .unwrap();
3442
3443    // Join the project as client B.
3444    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3445    let (_window_b, workspace_b) =
3446        cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
3447    let editor_b = workspace_b
3448        .update(cx_b, |workspace, cx| {
3449            workspace.open_path((worktree_id, "main.rs"), true, cx)
3450        })
3451        .await
3452        .unwrap()
3453        .downcast::<Editor>()
3454        .unwrap();
3455
3456    let mut fake_language_server = fake_language_servers.next().await.unwrap();
3457    fake_language_server
3458        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
3459            assert_eq!(
3460                params.text_document.uri,
3461                lsp::Url::from_file_path("/a/main.rs").unwrap(),
3462            );
3463            assert_eq!(params.range.start, lsp::Position::new(0, 0));
3464            assert_eq!(params.range.end, lsp::Position::new(0, 0));
3465            Ok(None)
3466        })
3467        .next()
3468        .await;
3469
3470    // Move cursor to a location that contains code actions.
3471    editor_b.update(cx_b, |editor, cx| {
3472        editor.change_selections(None, cx, |s| {
3473            s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
3474        });
3475        cx.focus(&editor_b);
3476    });
3477
3478    fake_language_server
3479        .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
3480            assert_eq!(
3481                params.text_document.uri,
3482                lsp::Url::from_file_path("/a/main.rs").unwrap(),
3483            );
3484            assert_eq!(params.range.start, lsp::Position::new(1, 31));
3485            assert_eq!(params.range.end, lsp::Position::new(1, 31));
3486
3487            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
3488                lsp::CodeAction {
3489                    title: "Inline into all callers".to_string(),
3490                    edit: Some(lsp::WorkspaceEdit {
3491                        changes: Some(
3492                            [
3493                                (
3494                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
3495                                    vec![lsp::TextEdit::new(
3496                                        lsp::Range::new(
3497                                            lsp::Position::new(1, 22),
3498                                            lsp::Position::new(1, 34),
3499                                        ),
3500                                        "4".to_string(),
3501                                    )],
3502                                ),
3503                                (
3504                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
3505                                    vec![lsp::TextEdit::new(
3506                                        lsp::Range::new(
3507                                            lsp::Position::new(0, 0),
3508                                            lsp::Position::new(0, 27),
3509                                        ),
3510                                        "".to_string(),
3511                                    )],
3512                                ),
3513                            ]
3514                            .into_iter()
3515                            .collect(),
3516                        ),
3517                        ..Default::default()
3518                    }),
3519                    data: Some(json!({
3520                        "codeActionParams": {
3521                            "range": {
3522                                "start": {"line": 1, "column": 31},
3523                                "end": {"line": 1, "column": 31},
3524                            }
3525                        }
3526                    })),
3527                    ..Default::default()
3528                },
3529            )]))
3530        })
3531        .next()
3532        .await;
3533
3534    // Toggle code actions and wait for them to display.
3535    editor_b.update(cx_b, |editor, cx| {
3536        editor.toggle_code_actions(
3537            &ToggleCodeActions {
3538                deployed_from_indicator: false,
3539            },
3540            cx,
3541        );
3542    });
3543    editor_b
3544        .condition(cx_b, |editor, _| editor.context_menu_visible())
3545        .await;
3546
3547    fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
3548
3549    // Confirming the code action will trigger a resolve request.
3550    let confirm_action = workspace_b
3551        .update(cx_b, |workspace, cx| {
3552            Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
3553        })
3554        .unwrap();
3555    fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
3556        |_, _| async move {
3557            Ok(lsp::CodeAction {
3558                title: "Inline into all callers".to_string(),
3559                edit: Some(lsp::WorkspaceEdit {
3560                    changes: Some(
3561                        [
3562                            (
3563                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
3564                                vec![lsp::TextEdit::new(
3565                                    lsp::Range::new(
3566                                        lsp::Position::new(1, 22),
3567                                        lsp::Position::new(1, 34),
3568                                    ),
3569                                    "4".to_string(),
3570                                )],
3571                            ),
3572                            (
3573                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
3574                                vec![lsp::TextEdit::new(
3575                                    lsp::Range::new(
3576                                        lsp::Position::new(0, 0),
3577                                        lsp::Position::new(0, 27),
3578                                    ),
3579                                    "".to_string(),
3580                                )],
3581                            ),
3582                        ]
3583                        .into_iter()
3584                        .collect(),
3585                    ),
3586                    ..Default::default()
3587                }),
3588                ..Default::default()
3589            })
3590        },
3591    );
3592
3593    // After the action is confirmed, an editor containing both modified files is opened.
3594    confirm_action.await.unwrap();
3595    let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
3596        workspace
3597            .active_item(cx)
3598            .unwrap()
3599            .downcast::<Editor>()
3600            .unwrap()
3601    });
3602    code_action_editor.update(cx_b, |editor, cx| {
3603        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
3604        editor.undo(&Undo, cx);
3605        assert_eq!(
3606            editor.text(cx),
3607            "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
3608        );
3609        editor.redo(&Redo, cx);
3610        assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
3611    });
3612}
3613
3614#[gpui::test(iterations = 10)]
3615async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3616    cx_a.foreground().forbid_parking();
3617    cx_b.update(editor::init);
3618    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3619    let client_a = server.create_client(cx_a, "user_a").await;
3620    let client_b = server.create_client(cx_b, "user_b").await;
3621    server
3622        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3623        .await;
3624    let active_call_a = cx_a.read(ActiveCall::global);
3625
3626    // Set up a fake language server.
3627    let mut language = Language::new(
3628        LanguageConfig {
3629            name: "Rust".into(),
3630            path_suffixes: vec!["rs".to_string()],
3631            ..Default::default()
3632        },
3633        Some(tree_sitter_rust::language()),
3634    );
3635    let mut fake_language_servers = language
3636        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3637            capabilities: lsp::ServerCapabilities {
3638                rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
3639                    prepare_provider: Some(true),
3640                    work_done_progress_options: Default::default(),
3641                })),
3642                ..Default::default()
3643            },
3644            ..Default::default()
3645        }))
3646        .await;
3647    client_a.language_registry.add(Arc::new(language));
3648
3649    client_a
3650        .fs
3651        .insert_tree(
3652            "/dir",
3653            json!({
3654                "one.rs": "const ONE: usize = 1;",
3655                "two.rs": "const TWO: usize = one::ONE + one::ONE;"
3656            }),
3657        )
3658        .await;
3659    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3660    let project_id = active_call_a
3661        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3662        .await
3663        .unwrap();
3664    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3665
3666    let (_window_b, workspace_b) =
3667        cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx));
3668    let editor_b = workspace_b
3669        .update(cx_b, |workspace, cx| {
3670            workspace.open_path((worktree_id, "one.rs"), true, cx)
3671        })
3672        .await
3673        .unwrap()
3674        .downcast::<Editor>()
3675        .unwrap();
3676    let fake_language_server = fake_language_servers.next().await.unwrap();
3677
3678    // Move cursor to a location that can be renamed.
3679    let prepare_rename = editor_b.update(cx_b, |editor, cx| {
3680        editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
3681        editor.rename(&Rename, cx).unwrap()
3682    });
3683
3684    fake_language_server
3685        .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
3686            assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
3687            assert_eq!(params.position, lsp::Position::new(0, 7));
3688            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
3689                lsp::Position::new(0, 6),
3690                lsp::Position::new(0, 9),
3691            ))))
3692        })
3693        .next()
3694        .await
3695        .unwrap();
3696    prepare_rename.await.unwrap();
3697    editor_b.update(cx_b, |editor, cx| {
3698        let rename = editor.pending_rename().unwrap();
3699        let buffer = editor.buffer().read(cx).snapshot(cx);
3700        assert_eq!(
3701            rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
3702            6..9
3703        );
3704        rename.editor.update(cx, |rename_editor, cx| {
3705            rename_editor.buffer().update(cx, |rename_buffer, cx| {
3706                rename_buffer.edit([(0..3, "THREE")], None, cx);
3707            });
3708        });
3709    });
3710
3711    let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
3712        Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
3713    });
3714    fake_language_server
3715        .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
3716            assert_eq!(
3717                params.text_document_position.text_document.uri.as_str(),
3718                "file:///dir/one.rs"
3719            );
3720            assert_eq!(
3721                params.text_document_position.position,
3722                lsp::Position::new(0, 6)
3723            );
3724            assert_eq!(params.new_name, "THREE");
3725            Ok(Some(lsp::WorkspaceEdit {
3726                changes: Some(
3727                    [
3728                        (
3729                            lsp::Url::from_file_path("/dir/one.rs").unwrap(),
3730                            vec![lsp::TextEdit::new(
3731                                lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
3732                                "THREE".to_string(),
3733                            )],
3734                        ),
3735                        (
3736                            lsp::Url::from_file_path("/dir/two.rs").unwrap(),
3737                            vec![
3738                                lsp::TextEdit::new(
3739                                    lsp::Range::new(
3740                                        lsp::Position::new(0, 24),
3741                                        lsp::Position::new(0, 27),
3742                                    ),
3743                                    "THREE".to_string(),
3744                                ),
3745                                lsp::TextEdit::new(
3746                                    lsp::Range::new(
3747                                        lsp::Position::new(0, 35),
3748                                        lsp::Position::new(0, 38),
3749                                    ),
3750                                    "THREE".to_string(),
3751                                ),
3752                            ],
3753                        ),
3754                    ]
3755                    .into_iter()
3756                    .collect(),
3757                ),
3758                ..Default::default()
3759            }))
3760        })
3761        .next()
3762        .await
3763        .unwrap();
3764    confirm_rename.await.unwrap();
3765
3766    let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
3767        workspace
3768            .active_item(cx)
3769            .unwrap()
3770            .downcast::<Editor>()
3771            .unwrap()
3772    });
3773    rename_editor.update(cx_b, |editor, cx| {
3774        assert_eq!(
3775            editor.text(cx),
3776            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3777        );
3778        editor.undo(&Undo, cx);
3779        assert_eq!(
3780            editor.text(cx),
3781            "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
3782        );
3783        editor.redo(&Redo, cx);
3784        assert_eq!(
3785            editor.text(cx),
3786            "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
3787        );
3788    });
3789
3790    // Ensure temporary rename edits cannot be undone/redone.
3791    editor_b.update(cx_b, |editor, cx| {
3792        editor.undo(&Undo, cx);
3793        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3794        editor.undo(&Undo, cx);
3795        assert_eq!(editor.text(cx), "const ONE: usize = 1;");
3796        editor.redo(&Redo, cx);
3797        assert_eq!(editor.text(cx), "const THREE: usize = 1;");
3798    })
3799}
3800
3801#[gpui::test(iterations = 10)]
3802async fn test_language_server_statuses(
3803    deterministic: Arc<Deterministic>,
3804    cx_a: &mut TestAppContext,
3805    cx_b: &mut TestAppContext,
3806) {
3807    deterministic.forbid_parking();
3808
3809    cx_b.update(editor::init);
3810    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3811    let client_a = server.create_client(cx_a, "user_a").await;
3812    let client_b = server.create_client(cx_b, "user_b").await;
3813    server
3814        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3815        .await;
3816    let active_call_a = cx_a.read(ActiveCall::global);
3817
3818    // Set up a fake language server.
3819    let mut language = Language::new(
3820        LanguageConfig {
3821            name: "Rust".into(),
3822            path_suffixes: vec!["rs".to_string()],
3823            ..Default::default()
3824        },
3825        Some(tree_sitter_rust::language()),
3826    );
3827    let mut fake_language_servers = language
3828        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
3829            name: "the-language-server",
3830            ..Default::default()
3831        }))
3832        .await;
3833    client_a.language_registry.add(Arc::new(language));
3834
3835    client_a
3836        .fs
3837        .insert_tree(
3838            "/dir",
3839            json!({
3840                "main.rs": "const ONE: usize = 1;",
3841            }),
3842        )
3843        .await;
3844    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3845
3846    let _buffer_a = project_a
3847        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
3848        .await
3849        .unwrap();
3850
3851    let fake_language_server = fake_language_servers.next().await.unwrap();
3852    fake_language_server.start_progress("the-token").await;
3853    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3854        token: lsp::NumberOrString::String("the-token".to_string()),
3855        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3856            lsp::WorkDoneProgressReport {
3857                message: Some("the-message".to_string()),
3858                ..Default::default()
3859            },
3860        )),
3861    });
3862    deterministic.run_until_parked();
3863    project_a.read_with(cx_a, |project, _| {
3864        let status = project.language_server_statuses().next().unwrap();
3865        assert_eq!(status.name, "the-language-server");
3866        assert_eq!(status.pending_work.len(), 1);
3867        assert_eq!(
3868            status.pending_work["the-token"].message.as_ref().unwrap(),
3869            "the-message"
3870        );
3871    });
3872
3873    let project_id = active_call_a
3874        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3875        .await
3876        .unwrap();
3877    deterministic.run_until_parked();
3878    let project_b = client_b.build_remote_project(project_id, cx_b).await;
3879    project_b.read_with(cx_b, |project, _| {
3880        let status = project.language_server_statuses().next().unwrap();
3881        assert_eq!(status.name, "the-language-server");
3882    });
3883
3884    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
3885        token: lsp::NumberOrString::String("the-token".to_string()),
3886        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
3887            lsp::WorkDoneProgressReport {
3888                message: Some("the-message-2".to_string()),
3889                ..Default::default()
3890            },
3891        )),
3892    });
3893    deterministic.run_until_parked();
3894    project_a.read_with(cx_a, |project, _| {
3895        let status = project.language_server_statuses().next().unwrap();
3896        assert_eq!(status.name, "the-language-server");
3897        assert_eq!(status.pending_work.len(), 1);
3898        assert_eq!(
3899            status.pending_work["the-token"].message.as_ref().unwrap(),
3900            "the-message-2"
3901        );
3902    });
3903    project_b.read_with(cx_b, |project, _| {
3904        let status = project.language_server_statuses().next().unwrap();
3905        assert_eq!(status.name, "the-language-server");
3906        assert_eq!(status.pending_work.len(), 1);
3907        assert_eq!(
3908            status.pending_work["the-token"].message.as_ref().unwrap(),
3909            "the-message-2"
3910        );
3911    });
3912}
3913
3914#[gpui::test(iterations = 10)]
3915async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
3916    cx_a.foreground().forbid_parking();
3917    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
3918    let client_a = server.create_client(cx_a, "user_a").await;
3919    let client_b = server.create_client(cx_b, "user_b").await;
3920
3921    // Create an org that includes these 2 users.
3922    let db = &server.app_state.db;
3923    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
3924    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
3925        .await
3926        .unwrap();
3927    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
3928        .await
3929        .unwrap();
3930
3931    // Create a channel that includes all the users.
3932    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
3933    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
3934        .await
3935        .unwrap();
3936    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
3937        .await
3938        .unwrap();
3939    db.create_channel_message(
3940        channel_id,
3941        client_b.current_user_id(cx_b),
3942        "hello A, it's B.",
3943        OffsetDateTime::now_utc(),
3944        1,
3945    )
3946    .await
3947    .unwrap();
3948
3949    let channels_a =
3950        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
3951    channels_a
3952        .condition(cx_a, |list, _| list.available_channels().is_some())
3953        .await;
3954    channels_a.read_with(cx_a, |list, _| {
3955        assert_eq!(
3956            list.available_channels().unwrap(),
3957            &[ChannelDetails {
3958                id: channel_id.to_proto(),
3959                name: "test-channel".to_string()
3960            }]
3961        )
3962    });
3963    let channel_a = channels_a.update(cx_a, |this, cx| {
3964        this.get_channel(channel_id.to_proto(), cx).unwrap()
3965    });
3966    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
3967    channel_a
3968        .condition(cx_a, |channel, _| {
3969            channel_messages(channel)
3970                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3971        })
3972        .await;
3973
3974    let channels_b =
3975        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
3976    channels_b
3977        .condition(cx_b, |list, _| list.available_channels().is_some())
3978        .await;
3979    channels_b.read_with(cx_b, |list, _| {
3980        assert_eq!(
3981            list.available_channels().unwrap(),
3982            &[ChannelDetails {
3983                id: channel_id.to_proto(),
3984                name: "test-channel".to_string()
3985            }]
3986        )
3987    });
3988
3989    let channel_b = channels_b.update(cx_b, |this, cx| {
3990        this.get_channel(channel_id.to_proto(), cx).unwrap()
3991    });
3992    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
3993    channel_b
3994        .condition(cx_b, |channel, _| {
3995            channel_messages(channel)
3996                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
3997        })
3998        .await;
3999
4000    channel_a
4001        .update(cx_a, |channel, cx| {
4002            channel
4003                .send_message("oh, hi B.".to_string(), cx)
4004                .unwrap()
4005                .detach();
4006            let task = channel.send_message("sup".to_string(), cx).unwrap();
4007            assert_eq!(
4008                channel_messages(channel),
4009                &[
4010                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4011                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
4012                    ("user_a".to_string(), "sup".to_string(), true)
4013                ]
4014            );
4015            task
4016        })
4017        .await
4018        .unwrap();
4019
4020    channel_b
4021        .condition(cx_b, |channel, _| {
4022            channel_messages(channel)
4023                == [
4024                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4025                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
4026                    ("user_a".to_string(), "sup".to_string(), false),
4027                ]
4028        })
4029        .await;
4030
4031    assert_eq!(
4032        server
4033            .store()
4034            .await
4035            .channel(channel_id)
4036            .unwrap()
4037            .connection_ids
4038            .len(),
4039        2
4040    );
4041    cx_b.update(|_| drop(channel_b));
4042    server
4043        .condition(|state| state.channel(channel_id).unwrap().connection_ids.len() == 1)
4044        .await;
4045
4046    cx_a.update(|_| drop(channel_a));
4047    server
4048        .condition(|state| state.channel(channel_id).is_none())
4049        .await;
4050}
4051
4052#[gpui::test(iterations = 10)]
4053async fn test_chat_message_validation(cx_a: &mut TestAppContext) {
4054    cx_a.foreground().forbid_parking();
4055    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4056    let client_a = server.create_client(cx_a, "user_a").await;
4057
4058    let db = &server.app_state.db;
4059    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
4060    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
4061    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
4062        .await
4063        .unwrap();
4064    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
4065        .await
4066        .unwrap();
4067
4068    let channels_a =
4069        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
4070    channels_a
4071        .condition(cx_a, |list, _| list.available_channels().is_some())
4072        .await;
4073    let channel_a = channels_a.update(cx_a, |this, cx| {
4074        this.get_channel(channel_id.to_proto(), cx).unwrap()
4075    });
4076
4077    // Messages aren't allowed to be too long.
4078    channel_a
4079        .update(cx_a, |channel, cx| {
4080            let long_body = "this is long.\n".repeat(1024);
4081            channel.send_message(long_body, cx).unwrap()
4082        })
4083        .await
4084        .unwrap_err();
4085
4086    // Messages aren't allowed to be blank.
4087    channel_a.update(cx_a, |channel, cx| {
4088        channel.send_message(String::new(), cx).unwrap_err()
4089    });
4090
4091    // Leading and trailing whitespace are trimmed.
4092    channel_a
4093        .update(cx_a, |channel, cx| {
4094            channel
4095                .send_message("\n surrounded by whitespace  \n".to_string(), cx)
4096                .unwrap()
4097        })
4098        .await
4099        .unwrap();
4100    assert_eq!(
4101        db.get_channel_messages(channel_id, 10, None)
4102            .await
4103            .unwrap()
4104            .iter()
4105            .map(|m| &m.body)
4106            .collect::<Vec<_>>(),
4107        &["surrounded by whitespace"]
4108    );
4109}
4110
4111#[gpui::test(iterations = 10)]
4112async fn test_chat_reconnection(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4113    cx_a.foreground().forbid_parking();
4114    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4115    let client_a = server.create_client(cx_a, "user_a").await;
4116    let client_b = server.create_client(cx_b, "user_b").await;
4117
4118    let mut status_b = client_b.status();
4119
4120    // Create an org that includes these 2 users.
4121    let db = &server.app_state.db;
4122    let org_id = db.create_org("Test Org", "test-org").await.unwrap();
4123    db.add_org_member(org_id, client_a.current_user_id(cx_a), false)
4124        .await
4125        .unwrap();
4126    db.add_org_member(org_id, client_b.current_user_id(cx_b), false)
4127        .await
4128        .unwrap();
4129
4130    // Create a channel that includes all the users.
4131    let channel_id = db.create_org_channel(org_id, "test-channel").await.unwrap();
4132    db.add_channel_member(channel_id, client_a.current_user_id(cx_a), false)
4133        .await
4134        .unwrap();
4135    db.add_channel_member(channel_id, client_b.current_user_id(cx_b), false)
4136        .await
4137        .unwrap();
4138    db.create_channel_message(
4139        channel_id,
4140        client_b.current_user_id(cx_b),
4141        "hello A, it's B.",
4142        OffsetDateTime::now_utc(),
4143        2,
4144    )
4145    .await
4146    .unwrap();
4147
4148    let channels_a =
4149        cx_a.add_model(|cx| ChannelList::new(client_a.user_store.clone(), client_a.clone(), cx));
4150    channels_a
4151        .condition(cx_a, |list, _| list.available_channels().is_some())
4152        .await;
4153
4154    channels_a.read_with(cx_a, |list, _| {
4155        assert_eq!(
4156            list.available_channels().unwrap(),
4157            &[ChannelDetails {
4158                id: channel_id.to_proto(),
4159                name: "test-channel".to_string()
4160            }]
4161        )
4162    });
4163    let channel_a = channels_a.update(cx_a, |this, cx| {
4164        this.get_channel(channel_id.to_proto(), cx).unwrap()
4165    });
4166    channel_a.read_with(cx_a, |channel, _| assert!(channel.messages().is_empty()));
4167    channel_a
4168        .condition(cx_a, |channel, _| {
4169            channel_messages(channel)
4170                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
4171        })
4172        .await;
4173
4174    let channels_b =
4175        cx_b.add_model(|cx| ChannelList::new(client_b.user_store.clone(), client_b.clone(), cx));
4176    channels_b
4177        .condition(cx_b, |list, _| list.available_channels().is_some())
4178        .await;
4179    channels_b.read_with(cx_b, |list, _| {
4180        assert_eq!(
4181            list.available_channels().unwrap(),
4182            &[ChannelDetails {
4183                id: channel_id.to_proto(),
4184                name: "test-channel".to_string()
4185            }]
4186        )
4187    });
4188
4189    let channel_b = channels_b.update(cx_b, |this, cx| {
4190        this.get_channel(channel_id.to_proto(), cx).unwrap()
4191    });
4192    channel_b.read_with(cx_b, |channel, _| assert!(channel.messages().is_empty()));
4193    channel_b
4194        .condition(cx_b, |channel, _| {
4195            channel_messages(channel)
4196                == [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
4197        })
4198        .await;
4199
4200    // Disconnect client B, ensuring we can still access its cached channel data.
4201    server.forbid_connections();
4202    server.disconnect_client(client_b.current_user_id(cx_b));
4203    cx_b.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
4204    while !matches!(
4205        status_b.next().await,
4206        Some(client::Status::ReconnectionError { .. })
4207    ) {}
4208
4209    channels_b.read_with(cx_b, |channels, _| {
4210        assert_eq!(
4211            channels.available_channels().unwrap(),
4212            [ChannelDetails {
4213                id: channel_id.to_proto(),
4214                name: "test-channel".to_string()
4215            }]
4216        )
4217    });
4218    channel_b.read_with(cx_b, |channel, _| {
4219        assert_eq!(
4220            channel_messages(channel),
4221            [("user_b".to_string(), "hello A, it's B.".to_string(), false)]
4222        )
4223    });
4224
4225    // Send a message from client B while it is disconnected.
4226    channel_b
4227        .update(cx_b, |channel, cx| {
4228            let task = channel
4229                .send_message("can you see this?".to_string(), cx)
4230                .unwrap();
4231            assert_eq!(
4232                channel_messages(channel),
4233                &[
4234                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4235                    ("user_b".to_string(), "can you see this?".to_string(), true)
4236                ]
4237            );
4238            task
4239        })
4240        .await
4241        .unwrap_err();
4242
4243    // Send a message from client A while B is disconnected.
4244    channel_a
4245        .update(cx_a, |channel, cx| {
4246            channel
4247                .send_message("oh, hi B.".to_string(), cx)
4248                .unwrap()
4249                .detach();
4250            let task = channel.send_message("sup".to_string(), cx).unwrap();
4251            assert_eq!(
4252                channel_messages(channel),
4253                &[
4254                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4255                    ("user_a".to_string(), "oh, hi B.".to_string(), true),
4256                    ("user_a".to_string(), "sup".to_string(), true)
4257                ]
4258            );
4259            task
4260        })
4261        .await
4262        .unwrap();
4263
4264    // Give client B a chance to reconnect.
4265    server.allow_connections();
4266    cx_b.foreground().advance_clock(Duration::from_secs(10));
4267
4268    // Verify that B sees the new messages upon reconnection, as well as the message client B
4269    // sent while offline.
4270    channel_b
4271        .condition(cx_b, |channel, _| {
4272            channel_messages(channel)
4273                == [
4274                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4275                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
4276                    ("user_a".to_string(), "sup".to_string(), false),
4277                    ("user_b".to_string(), "can you see this?".to_string(), false),
4278                ]
4279        })
4280        .await;
4281
4282    // Ensure client A and B can communicate normally after reconnection.
4283    channel_a
4284        .update(cx_a, |channel, cx| {
4285            channel.send_message("you online?".to_string(), cx).unwrap()
4286        })
4287        .await
4288        .unwrap();
4289    channel_b
4290        .condition(cx_b, |channel, _| {
4291            channel_messages(channel)
4292                == [
4293                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4294                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
4295                    ("user_a".to_string(), "sup".to_string(), false),
4296                    ("user_b".to_string(), "can you see this?".to_string(), false),
4297                    ("user_a".to_string(), "you online?".to_string(), false),
4298                ]
4299        })
4300        .await;
4301
4302    channel_b
4303        .update(cx_b, |channel, cx| {
4304            channel.send_message("yep".to_string(), cx).unwrap()
4305        })
4306        .await
4307        .unwrap();
4308    channel_a
4309        .condition(cx_a, |channel, _| {
4310            channel_messages(channel)
4311                == [
4312                    ("user_b".to_string(), "hello A, it's B.".to_string(), false),
4313                    ("user_a".to_string(), "oh, hi B.".to_string(), false),
4314                    ("user_a".to_string(), "sup".to_string(), false),
4315                    ("user_b".to_string(), "can you see this?".to_string(), false),
4316                    ("user_a".to_string(), "you online?".to_string(), false),
4317                    ("user_b".to_string(), "yep".to_string(), false),
4318                ]
4319        })
4320        .await;
4321}
4322
4323#[gpui::test(iterations = 10)]
4324async fn test_contacts(
4325    deterministic: Arc<Deterministic>,
4326    cx_a: &mut TestAppContext,
4327    cx_b: &mut TestAppContext,
4328    cx_c: &mut TestAppContext,
4329) {
4330    cx_a.foreground().forbid_parking();
4331    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4332    let client_a = server.create_client(cx_a, "user_a").await;
4333    let client_b = server.create_client(cx_b, "user_b").await;
4334    let client_c = server.create_client(cx_c, "user_c").await;
4335    server
4336        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4337        .await;
4338    let active_call_a = cx_a.read(ActiveCall::global);
4339    let active_call_b = cx_b.read(ActiveCall::global);
4340    let active_call_c = cx_c.read(ActiveCall::global);
4341
4342    deterministic.run_until_parked();
4343    assert_eq!(
4344        contacts(&client_a, cx_a),
4345        [
4346            ("user_b".to_string(), "online", "free"),
4347            ("user_c".to_string(), "online", "free")
4348        ]
4349    );
4350    assert_eq!(
4351        contacts(&client_b, cx_b),
4352        [
4353            ("user_a".to_string(), "online", "free"),
4354            ("user_c".to_string(), "online", "free")
4355        ]
4356    );
4357    assert_eq!(
4358        contacts(&client_c, cx_c),
4359        [
4360            ("user_a".to_string(), "online", "free"),
4361            ("user_b".to_string(), "online", "free")
4362        ]
4363    );
4364
4365    server.disconnect_client(client_c.current_user_id(cx_c));
4366    server.forbid_connections();
4367    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
4368    assert_eq!(
4369        contacts(&client_a, cx_a),
4370        [
4371            ("user_b".to_string(), "online", "free"),
4372            ("user_c".to_string(), "offline", "free")
4373        ]
4374    );
4375    assert_eq!(
4376        contacts(&client_b, cx_b),
4377        [
4378            ("user_a".to_string(), "online", "free"),
4379            ("user_c".to_string(), "offline", "free")
4380        ]
4381    );
4382    assert_eq!(contacts(&client_c, cx_c), []);
4383
4384    server.allow_connections();
4385    client_c
4386        .authenticate_and_connect(false, &cx_c.to_async())
4387        .await
4388        .unwrap();
4389
4390    deterministic.run_until_parked();
4391    assert_eq!(
4392        contacts(&client_a, cx_a),
4393        [
4394            ("user_b".to_string(), "online", "free"),
4395            ("user_c".to_string(), "online", "free")
4396        ]
4397    );
4398    assert_eq!(
4399        contacts(&client_b, cx_b),
4400        [
4401            ("user_a".to_string(), "online", "free"),
4402            ("user_c".to_string(), "online", "free")
4403        ]
4404    );
4405    assert_eq!(
4406        contacts(&client_c, cx_c),
4407        [
4408            ("user_a".to_string(), "online", "free"),
4409            ("user_b".to_string(), "online", "free")
4410        ]
4411    );
4412
4413    active_call_a
4414        .update(cx_a, |call, cx| {
4415            call.invite(client_b.user_id().unwrap(), None, cx)
4416        })
4417        .await
4418        .unwrap();
4419    deterministic.run_until_parked();
4420    assert_eq!(
4421        contacts(&client_a, cx_a),
4422        [
4423            ("user_b".to_string(), "online", "busy"),
4424            ("user_c".to_string(), "online", "free")
4425        ]
4426    );
4427    assert_eq!(
4428        contacts(&client_b, cx_b),
4429        [
4430            ("user_a".to_string(), "online", "busy"),
4431            ("user_c".to_string(), "online", "free")
4432        ]
4433    );
4434    assert_eq!(
4435        contacts(&client_c, cx_c),
4436        [
4437            ("user_a".to_string(), "online", "busy"),
4438            ("user_b".to_string(), "online", "busy")
4439        ]
4440    );
4441
4442    active_call_b.update(cx_b, |call, _| call.decline_incoming().unwrap());
4443    deterministic.run_until_parked();
4444    assert_eq!(
4445        contacts(&client_a, cx_a),
4446        [
4447            ("user_b".to_string(), "online", "free"),
4448            ("user_c".to_string(), "online", "free")
4449        ]
4450    );
4451    assert_eq!(
4452        contacts(&client_b, cx_b),
4453        [
4454            ("user_a".to_string(), "online", "free"),
4455            ("user_c".to_string(), "online", "free")
4456        ]
4457    );
4458    assert_eq!(
4459        contacts(&client_c, cx_c),
4460        [
4461            ("user_a".to_string(), "online", "free"),
4462            ("user_b".to_string(), "online", "free")
4463        ]
4464    );
4465
4466    active_call_c
4467        .update(cx_c, |call, cx| {
4468            call.invite(client_a.user_id().unwrap(), None, cx)
4469        })
4470        .await
4471        .unwrap();
4472    deterministic.run_until_parked();
4473    assert_eq!(
4474        contacts(&client_a, cx_a),
4475        [
4476            ("user_b".to_string(), "online", "free"),
4477            ("user_c".to_string(), "online", "busy")
4478        ]
4479    );
4480    assert_eq!(
4481        contacts(&client_b, cx_b),
4482        [
4483            ("user_a".to_string(), "online", "busy"),
4484            ("user_c".to_string(), "online", "busy")
4485        ]
4486    );
4487    assert_eq!(
4488        contacts(&client_c, cx_c),
4489        [
4490            ("user_a".to_string(), "online", "busy"),
4491            ("user_b".to_string(), "online", "free")
4492        ]
4493    );
4494
4495    active_call_a
4496        .update(cx_a, |call, cx| call.accept_incoming(cx))
4497        .await
4498        .unwrap();
4499    deterministic.run_until_parked();
4500    assert_eq!(
4501        contacts(&client_a, cx_a),
4502        [
4503            ("user_b".to_string(), "online", "free"),
4504            ("user_c".to_string(), "online", "busy")
4505        ]
4506    );
4507    assert_eq!(
4508        contacts(&client_b, cx_b),
4509        [
4510            ("user_a".to_string(), "online", "busy"),
4511            ("user_c".to_string(), "online", "busy")
4512        ]
4513    );
4514    assert_eq!(
4515        contacts(&client_c, cx_c),
4516        [
4517            ("user_a".to_string(), "online", "busy"),
4518            ("user_b".to_string(), "online", "free")
4519        ]
4520    );
4521
4522    active_call_a
4523        .update(cx_a, |call, cx| {
4524            call.invite(client_b.user_id().unwrap(), None, cx)
4525        })
4526        .await
4527        .unwrap();
4528    deterministic.run_until_parked();
4529    assert_eq!(
4530        contacts(&client_a, cx_a),
4531        [
4532            ("user_b".to_string(), "online", "busy"),
4533            ("user_c".to_string(), "online", "busy")
4534        ]
4535    );
4536    assert_eq!(
4537        contacts(&client_b, cx_b),
4538        [
4539            ("user_a".to_string(), "online", "busy"),
4540            ("user_c".to_string(), "online", "busy")
4541        ]
4542    );
4543    assert_eq!(
4544        contacts(&client_c, cx_c),
4545        [
4546            ("user_a".to_string(), "online", "busy"),
4547            ("user_b".to_string(), "online", "busy")
4548        ]
4549    );
4550
4551    active_call_a.update(cx_a, |call, cx| call.hang_up(cx).unwrap());
4552    deterministic.run_until_parked();
4553    assert_eq!(
4554        contacts(&client_a, cx_a),
4555        [
4556            ("user_b".to_string(), "online", "free"),
4557            ("user_c".to_string(), "online", "free")
4558        ]
4559    );
4560    assert_eq!(
4561        contacts(&client_b, cx_b),
4562        [
4563            ("user_a".to_string(), "online", "free"),
4564            ("user_c".to_string(), "online", "free")
4565        ]
4566    );
4567    assert_eq!(
4568        contacts(&client_c, cx_c),
4569        [
4570            ("user_a".to_string(), "online", "free"),
4571            ("user_b".to_string(), "online", "free")
4572        ]
4573    );
4574
4575    active_call_a
4576        .update(cx_a, |call, cx| {
4577            call.invite(client_b.user_id().unwrap(), None, cx)
4578        })
4579        .await
4580        .unwrap();
4581    deterministic.run_until_parked();
4582    assert_eq!(
4583        contacts(&client_a, cx_a),
4584        [
4585            ("user_b".to_string(), "online", "busy"),
4586            ("user_c".to_string(), "online", "free")
4587        ]
4588    );
4589    assert_eq!(
4590        contacts(&client_b, cx_b),
4591        [
4592            ("user_a".to_string(), "online", "busy"),
4593            ("user_c".to_string(), "online", "free")
4594        ]
4595    );
4596    assert_eq!(
4597        contacts(&client_c, cx_c),
4598        [
4599            ("user_a".to_string(), "online", "busy"),
4600            ("user_b".to_string(), "online", "busy")
4601        ]
4602    );
4603
4604    server.forbid_connections();
4605    server.disconnect_client(client_a.current_user_id(cx_a));
4606    deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
4607    assert_eq!(contacts(&client_a, cx_a), []);
4608    assert_eq!(
4609        contacts(&client_b, cx_b),
4610        [
4611            ("user_a".to_string(), "offline", "free"),
4612            ("user_c".to_string(), "online", "free")
4613        ]
4614    );
4615    assert_eq!(
4616        contacts(&client_c, cx_c),
4617        [
4618            ("user_a".to_string(), "offline", "free"),
4619            ("user_b".to_string(), "online", "free")
4620        ]
4621    );
4622
4623    #[allow(clippy::type_complexity)]
4624    fn contacts(
4625        client: &TestClient,
4626        cx: &TestAppContext,
4627    ) -> Vec<(String, &'static str, &'static str)> {
4628        client.user_store.read_with(cx, |store, _| {
4629            store
4630                .contacts()
4631                .iter()
4632                .map(|contact| {
4633                    (
4634                        contact.user.github_login.clone(),
4635                        if contact.online { "online" } else { "offline" },
4636                        if contact.busy { "busy" } else { "free" },
4637                    )
4638                })
4639                .collect()
4640        })
4641    }
4642}
4643
4644#[gpui::test(iterations = 10)]
4645async fn test_contact_requests(
4646    executor: Arc<Deterministic>,
4647    cx_a: &mut TestAppContext,
4648    cx_a2: &mut TestAppContext,
4649    cx_b: &mut TestAppContext,
4650    cx_b2: &mut TestAppContext,
4651    cx_c: &mut TestAppContext,
4652    cx_c2: &mut TestAppContext,
4653) {
4654    cx_a.foreground().forbid_parking();
4655
4656    // Connect to a server as 3 clients.
4657    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4658    let client_a = server.create_client(cx_a, "user_a").await;
4659    let client_a2 = server.create_client(cx_a2, "user_a").await;
4660    let client_b = server.create_client(cx_b, "user_b").await;
4661    let client_b2 = server.create_client(cx_b2, "user_b").await;
4662    let client_c = server.create_client(cx_c, "user_c").await;
4663    let client_c2 = server.create_client(cx_c2, "user_c").await;
4664
4665    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
4666    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
4667    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
4668
4669    // User A and User C request that user B become their contact.
4670    client_a
4671        .user_store
4672        .update(cx_a, |store, cx| {
4673            store.request_contact(client_b.user_id().unwrap(), cx)
4674        })
4675        .await
4676        .unwrap();
4677    client_c
4678        .user_store
4679        .update(cx_c, |store, cx| {
4680            store.request_contact(client_b.user_id().unwrap(), cx)
4681        })
4682        .await
4683        .unwrap();
4684    executor.run_until_parked();
4685
4686    // All users see the pending request appear in all their clients.
4687    assert_eq!(
4688        client_a.summarize_contacts(cx_a).outgoing_requests,
4689        &["user_b"]
4690    );
4691    assert_eq!(
4692        client_a2.summarize_contacts(cx_a2).outgoing_requests,
4693        &["user_b"]
4694    );
4695    assert_eq!(
4696        client_b.summarize_contacts(cx_b).incoming_requests,
4697        &["user_a", "user_c"]
4698    );
4699    assert_eq!(
4700        client_b2.summarize_contacts(cx_b2).incoming_requests,
4701        &["user_a", "user_c"]
4702    );
4703    assert_eq!(
4704        client_c.summarize_contacts(cx_c).outgoing_requests,
4705        &["user_b"]
4706    );
4707    assert_eq!(
4708        client_c2.summarize_contacts(cx_c2).outgoing_requests,
4709        &["user_b"]
4710    );
4711
4712    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
4713    disconnect_and_reconnect(&client_a, cx_a).await;
4714    disconnect_and_reconnect(&client_b, cx_b).await;
4715    disconnect_and_reconnect(&client_c, cx_c).await;
4716    executor.run_until_parked();
4717    assert_eq!(
4718        client_a.summarize_contacts(cx_a).outgoing_requests,
4719        &["user_b"]
4720    );
4721    assert_eq!(
4722        client_b.summarize_contacts(cx_b).incoming_requests,
4723        &["user_a", "user_c"]
4724    );
4725    assert_eq!(
4726        client_c.summarize_contacts(cx_c).outgoing_requests,
4727        &["user_b"]
4728    );
4729
4730    // User B accepts the request from user A.
4731    client_b
4732        .user_store
4733        .update(cx_b, |store, cx| {
4734            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
4735        })
4736        .await
4737        .unwrap();
4738
4739    executor.run_until_parked();
4740
4741    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
4742    let contacts_b = client_b.summarize_contacts(cx_b);
4743    assert_eq!(contacts_b.current, &["user_a"]);
4744    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
4745    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
4746    assert_eq!(contacts_b2.current, &["user_a"]);
4747    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
4748
4749    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
4750    let contacts_a = client_a.summarize_contacts(cx_a);
4751    assert_eq!(contacts_a.current, &["user_b"]);
4752    assert!(contacts_a.outgoing_requests.is_empty());
4753    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
4754    assert_eq!(contacts_a2.current, &["user_b"]);
4755    assert!(contacts_a2.outgoing_requests.is_empty());
4756
4757    // Contacts are present upon connecting (tested here via disconnect/reconnect)
4758    disconnect_and_reconnect(&client_a, cx_a).await;
4759    disconnect_and_reconnect(&client_b, cx_b).await;
4760    disconnect_and_reconnect(&client_c, cx_c).await;
4761    executor.run_until_parked();
4762    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
4763    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
4764    assert_eq!(
4765        client_b.summarize_contacts(cx_b).incoming_requests,
4766        &["user_c"]
4767    );
4768    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
4769    assert_eq!(
4770        client_c.summarize_contacts(cx_c).outgoing_requests,
4771        &["user_b"]
4772    );
4773
4774    // User B rejects the request from user C.
4775    client_b
4776        .user_store
4777        .update(cx_b, |store, cx| {
4778            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
4779        })
4780        .await
4781        .unwrap();
4782
4783    executor.run_until_parked();
4784
4785    // User B doesn't see user C as their contact, and the incoming request from them is removed.
4786    let contacts_b = client_b.summarize_contacts(cx_b);
4787    assert_eq!(contacts_b.current, &["user_a"]);
4788    assert!(contacts_b.incoming_requests.is_empty());
4789    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
4790    assert_eq!(contacts_b2.current, &["user_a"]);
4791    assert!(contacts_b2.incoming_requests.is_empty());
4792
4793    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
4794    let contacts_c = client_c.summarize_contacts(cx_c);
4795    assert!(contacts_c.current.is_empty());
4796    assert!(contacts_c.outgoing_requests.is_empty());
4797    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
4798    assert!(contacts_c2.current.is_empty());
4799    assert!(contacts_c2.outgoing_requests.is_empty());
4800
4801    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
4802    disconnect_and_reconnect(&client_a, cx_a).await;
4803    disconnect_and_reconnect(&client_b, cx_b).await;
4804    disconnect_and_reconnect(&client_c, cx_c).await;
4805    executor.run_until_parked();
4806    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
4807    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
4808    assert!(client_b
4809        .summarize_contacts(cx_b)
4810        .incoming_requests
4811        .is_empty());
4812    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
4813    assert!(client_c
4814        .summarize_contacts(cx_c)
4815        .outgoing_requests
4816        .is_empty());
4817
4818    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
4819        client.disconnect(&cx.to_async()).unwrap();
4820        client.clear_contacts(cx).await;
4821        client
4822            .authenticate_and_connect(false, &cx.to_async())
4823            .await
4824            .unwrap();
4825    }
4826}
4827
4828#[gpui::test(iterations = 10)]
4829async fn test_following(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
4830    cx_a.foreground().forbid_parking();
4831    cx_a.update(editor::init);
4832    cx_b.update(editor::init);
4833
4834    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
4835    let client_a = server.create_client(cx_a, "user_a").await;
4836    let client_b = server.create_client(cx_b, "user_b").await;
4837    server
4838        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4839        .await;
4840    let active_call_a = cx_a.read(ActiveCall::global);
4841
4842    client_a
4843        .fs
4844        .insert_tree(
4845            "/a",
4846            json!({
4847                "1.txt": "one",
4848                "2.txt": "two",
4849                "3.txt": "three",
4850            }),
4851        )
4852        .await;
4853    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
4854    let project_id = active_call_a
4855        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4856        .await
4857        .unwrap();
4858    let project_b = client_b.build_remote_project(project_id, cx_b).await;
4859
4860    // Client A opens some editors.
4861    let workspace_a = client_a.build_workspace(&project_a, cx_a);
4862    let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
4863    let editor_a1 = workspace_a
4864        .update(cx_a, |workspace, cx| {
4865            workspace.open_path((worktree_id, "1.txt"), true, cx)
4866        })
4867        .await
4868        .unwrap()
4869        .downcast::<Editor>()
4870        .unwrap();
4871    let editor_a2 = workspace_a
4872        .update(cx_a, |workspace, cx| {
4873            workspace.open_path((worktree_id, "2.txt"), true, cx)
4874        })
4875        .await
4876        .unwrap()
4877        .downcast::<Editor>()
4878        .unwrap();
4879
4880    // Client B opens an editor.
4881    let workspace_b = client_b.build_workspace(&project_b, cx_b);
4882    let editor_b1 = workspace_b
4883        .update(cx_b, |workspace, cx| {
4884            workspace.open_path((worktree_id, "1.txt"), true, cx)
4885        })
4886        .await
4887        .unwrap()
4888        .downcast::<Editor>()
4889        .unwrap();
4890
4891    let client_a_id = project_b.read_with(cx_b, |project, _| {
4892        project.collaborators().values().next().unwrap().peer_id
4893    });
4894    let client_b_id = project_a.read_with(cx_a, |project, _| {
4895        project.collaborators().values().next().unwrap().peer_id
4896    });
4897
4898    // When client B starts following client A, all visible view states are replicated to client B.
4899    editor_a1.update(cx_a, |editor, cx| {
4900        editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
4901    });
4902    editor_a2.update(cx_a, |editor, cx| {
4903        editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
4904    });
4905    workspace_b
4906        .update(cx_b, |workspace, cx| {
4907            workspace
4908                .toggle_follow(&ToggleFollow(client_a_id), cx)
4909                .unwrap()
4910        })
4911        .await
4912        .unwrap();
4913
4914    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
4915        workspace
4916            .active_item(cx)
4917            .unwrap()
4918            .downcast::<Editor>()
4919            .unwrap()
4920    });
4921    assert!(cx_b.read(|cx| editor_b2.is_focused(cx)));
4922    assert_eq!(
4923        editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
4924        Some((worktree_id, "2.txt").into())
4925    );
4926    assert_eq!(
4927        editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4928        vec![2..3]
4929    );
4930    assert_eq!(
4931        editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
4932        vec![0..1]
4933    );
4934
4935    // When client A activates a different editor, client B does so as well.
4936    workspace_a.update(cx_a, |workspace, cx| {
4937        workspace.activate_item(&editor_a1, cx)
4938    });
4939    workspace_b
4940        .condition(cx_b, |workspace, cx| {
4941            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4942        })
4943        .await;
4944
4945    // When client A navigates back and forth, client B does so as well.
4946    workspace_a
4947        .update(cx_a, |workspace, cx| {
4948            workspace::Pane::go_back(workspace, None, cx)
4949        })
4950        .await;
4951    workspace_b
4952        .condition(cx_b, |workspace, cx| {
4953            workspace.active_item(cx).unwrap().id() == editor_b2.id()
4954        })
4955        .await;
4956
4957    workspace_a
4958        .update(cx_a, |workspace, cx| {
4959            workspace::Pane::go_forward(workspace, None, cx)
4960        })
4961        .await;
4962    workspace_b
4963        .condition(cx_b, |workspace, cx| {
4964            workspace.active_item(cx).unwrap().id() == editor_b1.id()
4965        })
4966        .await;
4967
4968    // Changes to client A's editor are reflected on client B.
4969    editor_a1.update(cx_a, |editor, cx| {
4970        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
4971    });
4972    editor_b1
4973        .condition(cx_b, |editor, cx| {
4974            editor.selections.ranges(cx) == vec![1..1, 2..2]
4975        })
4976        .await;
4977
4978    editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
4979    editor_b1
4980        .condition(cx_b, |editor, cx| editor.text(cx) == "TWO")
4981        .await;
4982
4983    editor_a1.update(cx_a, |editor, cx| {
4984        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4985        editor.set_scroll_position(vec2f(0., 100.), cx);
4986    });
4987    editor_b1
4988        .condition(cx_b, |editor, cx| {
4989            editor.selections.ranges(cx) == vec![3..3]
4990        })
4991        .await;
4992
4993    // After unfollowing, client B stops receiving updates from client A.
4994    workspace_b.update(cx_b, |workspace, cx| {
4995        workspace.unfollow(&workspace.active_pane().clone(), cx)
4996    });
4997    workspace_a.update(cx_a, |workspace, cx| {
4998        workspace.activate_item(&editor_a2, cx)
4999    });
5000    cx_a.foreground().run_until_parked();
5001    assert_eq!(
5002        workspace_b.read_with(cx_b, |workspace, cx| workspace
5003            .active_item(cx)
5004            .unwrap()
5005            .id()),
5006        editor_b1.id()
5007    );
5008
5009    // Client A starts following client B.
5010    workspace_a
5011        .update(cx_a, |workspace, cx| {
5012            workspace
5013                .toggle_follow(&ToggleFollow(client_b_id), cx)
5014                .unwrap()
5015        })
5016        .await
5017        .unwrap();
5018    assert_eq!(
5019        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5020        Some(client_b_id)
5021    );
5022    assert_eq!(
5023        workspace_a.read_with(cx_a, |workspace, cx| workspace
5024            .active_item(cx)
5025            .unwrap()
5026            .id()),
5027        editor_a1.id()
5028    );
5029
5030    // Following interrupts when client B disconnects.
5031    client_b.disconnect(&cx_b.to_async()).unwrap();
5032    cx_a.foreground().run_until_parked();
5033    assert_eq!(
5034        workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
5035        None
5036    );
5037}
5038
5039#[gpui::test(iterations = 10)]
5040async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5041    cx_a.foreground().forbid_parking();
5042    cx_a.update(editor::init);
5043    cx_b.update(editor::init);
5044
5045    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
5046    let client_a = server.create_client(cx_a, "user_a").await;
5047    let client_b = server.create_client(cx_b, "user_b").await;
5048    server
5049        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5050        .await;
5051    let active_call_a = cx_a.read(ActiveCall::global);
5052
5053    // Client A shares a project.
5054    client_a
5055        .fs
5056        .insert_tree(
5057            "/a",
5058            json!({
5059                "1.txt": "one",
5060                "2.txt": "two",
5061                "3.txt": "three",
5062                "4.txt": "four",
5063            }),
5064        )
5065        .await;
5066    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5067    let project_id = active_call_a
5068        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5069        .await
5070        .unwrap();
5071
5072    // Client B joins the project.
5073    let project_b = client_b.build_remote_project(project_id, cx_b).await;
5074
5075    // Client A opens some editors.
5076    let workspace_a = client_a.build_workspace(&project_a, cx_a);
5077    let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
5078    let _editor_a1 = workspace_a
5079        .update(cx_a, |workspace, cx| {
5080            workspace.open_path((worktree_id, "1.txt"), true, cx)
5081        })
5082        .await
5083        .unwrap()
5084        .downcast::<Editor>()
5085        .unwrap();
5086
5087    // Client B opens an editor.
5088    let workspace_b = client_b.build_workspace(&project_b, cx_b);
5089    let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5090    let _editor_b1 = workspace_b
5091        .update(cx_b, |workspace, cx| {
5092            workspace.open_path((worktree_id, "2.txt"), true, cx)
5093        })
5094        .await
5095        .unwrap()
5096        .downcast::<Editor>()
5097        .unwrap();
5098
5099    // Clients A and B follow each other in split panes
5100    workspace_a.update(cx_a, |workspace, cx| {
5101        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5102        let pane_a1 = pane_a1.clone();
5103        cx.defer(move |workspace, _| {
5104            assert_ne!(*workspace.active_pane(), pane_a1);
5105        });
5106    });
5107    workspace_a
5108        .update(cx_a, |workspace, cx| {
5109            let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
5110            workspace
5111                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5112                .unwrap()
5113        })
5114        .await
5115        .unwrap();
5116    workspace_b.update(cx_b, |workspace, cx| {
5117        workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, cx);
5118        let pane_b1 = pane_b1.clone();
5119        cx.defer(move |workspace, _| {
5120            assert_ne!(*workspace.active_pane(), pane_b1);
5121        });
5122    });
5123    workspace_b
5124        .update(cx_b, |workspace, cx| {
5125            let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
5126            workspace
5127                .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
5128                .unwrap()
5129        })
5130        .await
5131        .unwrap();
5132
5133    workspace_a.update(cx_a, |workspace, cx| {
5134        workspace.activate_next_pane(cx);
5135    });
5136    // Wait for focus effects to be fully flushed
5137    workspace_a.update(cx_a, |workspace, _| {
5138        assert_eq!(*workspace.active_pane(), pane_a1);
5139    });
5140
5141    workspace_a
5142        .update(cx_a, |workspace, cx| {
5143            workspace.open_path((worktree_id, "3.txt"), true, cx)
5144        })
5145        .await
5146        .unwrap();
5147    workspace_b.update(cx_b, |workspace, cx| {
5148        workspace.activate_next_pane(cx);
5149    });
5150
5151    workspace_b
5152        .update(cx_b, |workspace, cx| {
5153            assert_eq!(*workspace.active_pane(), pane_b1);
5154            workspace.open_path((worktree_id, "4.txt"), true, cx)
5155        })
5156        .await
5157        .unwrap();
5158    cx_a.foreground().run_until_parked();
5159
5160    // Ensure leader updates don't change the active pane of followers
5161    workspace_a.read_with(cx_a, |workspace, _| {
5162        assert_eq!(*workspace.active_pane(), pane_a1);
5163    });
5164    workspace_b.read_with(cx_b, |workspace, _| {
5165        assert_eq!(*workspace.active_pane(), pane_b1);
5166    });
5167
5168    // Ensure peers following each other doesn't cause an infinite loop.
5169    assert_eq!(
5170        workspace_a.read_with(cx_a, |workspace, cx| workspace
5171            .active_item(cx)
5172            .unwrap()
5173            .project_path(cx)),
5174        Some((worktree_id, "3.txt").into())
5175    );
5176    workspace_a.update(cx_a, |workspace, cx| {
5177        assert_eq!(
5178            workspace.active_item(cx).unwrap().project_path(cx),
5179            Some((worktree_id, "3.txt").into())
5180        );
5181        workspace.activate_next_pane(cx);
5182    });
5183
5184    workspace_a.update(cx_a, |workspace, cx| {
5185        assert_eq!(
5186            workspace.active_item(cx).unwrap().project_path(cx),
5187            Some((worktree_id, "4.txt").into())
5188        );
5189    });
5190
5191    workspace_b.update(cx_b, |workspace, cx| {
5192        assert_eq!(
5193            workspace.active_item(cx).unwrap().project_path(cx),
5194            Some((worktree_id, "4.txt").into())
5195        );
5196        workspace.activate_next_pane(cx);
5197    });
5198
5199    workspace_b.update(cx_b, |workspace, cx| {
5200        assert_eq!(
5201            workspace.active_item(cx).unwrap().project_path(cx),
5202            Some((worktree_id, "3.txt").into())
5203        );
5204    });
5205}
5206
5207#[gpui::test(iterations = 10)]
5208async fn test_auto_unfollowing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
5209    cx_a.foreground().forbid_parking();
5210    cx_a.update(editor::init);
5211    cx_b.update(editor::init);
5212
5213    // 2 clients connect to a server.
5214    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
5215    let client_a = server.create_client(cx_a, "user_a").await;
5216    let client_b = server.create_client(cx_b, "user_b").await;
5217    server
5218        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5219        .await;
5220    let active_call_a = cx_a.read(ActiveCall::global);
5221
5222    // Client A shares a project.
5223    client_a
5224        .fs
5225        .insert_tree(
5226            "/a",
5227            json!({
5228                "1.txt": "one",
5229                "2.txt": "two",
5230                "3.txt": "three",
5231            }),
5232        )
5233        .await;
5234    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
5235    let project_id = active_call_a
5236        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5237        .await
5238        .unwrap();
5239    let project_b = client_b.build_remote_project(project_id, cx_b).await;
5240
5241    // Client A opens some editors.
5242    let workspace_a = client_a.build_workspace(&project_a, cx_a);
5243    let _editor_a1 = workspace_a
5244        .update(cx_a, |workspace, cx| {
5245            workspace.open_path((worktree_id, "1.txt"), true, cx)
5246        })
5247        .await
5248        .unwrap()
5249        .downcast::<Editor>()
5250        .unwrap();
5251
5252    // Client B starts following client A.
5253    let workspace_b = client_b.build_workspace(&project_b, cx_b);
5254    let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
5255    let leader_id = project_b.read_with(cx_b, |project, _| {
5256        project.collaborators().values().next().unwrap().peer_id
5257    });
5258    workspace_b
5259        .update(cx_b, |workspace, cx| {
5260            workspace
5261                .toggle_follow(&ToggleFollow(leader_id), cx)
5262                .unwrap()
5263        })
5264        .await
5265        .unwrap();
5266    assert_eq!(
5267        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5268        Some(leader_id)
5269    );
5270    let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
5271        workspace
5272            .active_item(cx)
5273            .unwrap()
5274            .downcast::<Editor>()
5275            .unwrap()
5276    });
5277
5278    // When client B moves, it automatically stops following client A.
5279    editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
5280    assert_eq!(
5281        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5282        None
5283    );
5284
5285    workspace_b
5286        .update(cx_b, |workspace, cx| {
5287            workspace
5288                .toggle_follow(&ToggleFollow(leader_id), cx)
5289                .unwrap()
5290        })
5291        .await
5292        .unwrap();
5293    assert_eq!(
5294        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5295        Some(leader_id)
5296    );
5297
5298    // When client B edits, it automatically stops following client A.
5299    editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
5300    assert_eq!(
5301        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5302        None
5303    );
5304
5305    workspace_b
5306        .update(cx_b, |workspace, cx| {
5307            workspace
5308                .toggle_follow(&ToggleFollow(leader_id), cx)
5309                .unwrap()
5310        })
5311        .await
5312        .unwrap();
5313    assert_eq!(
5314        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5315        Some(leader_id)
5316    );
5317
5318    // When client B scrolls, it automatically stops following client A.
5319    editor_b2.update(cx_b, |editor, cx| {
5320        editor.set_scroll_position(vec2f(0., 3.), cx)
5321    });
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 activates a different pane, it continues following client A in the original pane.
5341    workspace_b.update(cx_b, |workspace, cx| {
5342        workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
5343    });
5344    assert_eq!(
5345        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5346        Some(leader_id)
5347    );
5348
5349    workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
5350    assert_eq!(
5351        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5352        Some(leader_id)
5353    );
5354
5355    // When client B activates a different item in the original pane, it automatically stops following client A.
5356    workspace_b
5357        .update(cx_b, |workspace, cx| {
5358            workspace.open_path((worktree_id, "2.txt"), true, cx)
5359        })
5360        .await
5361        .unwrap();
5362    assert_eq!(
5363        workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
5364        None
5365    );
5366}
5367
5368#[gpui::test(iterations = 10)]
5369async fn test_peers_simultaneously_following_each_other(
5370    deterministic: Arc<Deterministic>,
5371    cx_a: &mut TestAppContext,
5372    cx_b: &mut TestAppContext,
5373) {
5374    deterministic.forbid_parking();
5375    cx_a.update(editor::init);
5376    cx_b.update(editor::init);
5377
5378    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
5379    let client_a = server.create_client(cx_a, "user_a").await;
5380    let client_b = server.create_client(cx_b, "user_b").await;
5381    server
5382        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5383        .await;
5384    let active_call_a = cx_a.read(ActiveCall::global);
5385
5386    client_a.fs.insert_tree("/a", json!({})).await;
5387    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
5388    let workspace_a = client_a.build_workspace(&project_a, cx_a);
5389    let project_id = active_call_a
5390        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5391        .await
5392        .unwrap();
5393
5394    let project_b = client_b.build_remote_project(project_id, cx_b).await;
5395    let workspace_b = client_b.build_workspace(&project_b, cx_b);
5396
5397    deterministic.run_until_parked();
5398    let client_a_id = project_b.read_with(cx_b, |project, _| {
5399        project.collaborators().values().next().unwrap().peer_id
5400    });
5401    let client_b_id = project_a.read_with(cx_a, |project, _| {
5402        project.collaborators().values().next().unwrap().peer_id
5403    });
5404
5405    let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
5406        workspace
5407            .toggle_follow(&ToggleFollow(client_b_id), cx)
5408            .unwrap()
5409    });
5410    let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
5411        workspace
5412            .toggle_follow(&ToggleFollow(client_a_id), cx)
5413            .unwrap()
5414    });
5415
5416    futures::try_join!(a_follow_b, b_follow_a).unwrap();
5417    workspace_a.read_with(cx_a, |workspace, _| {
5418        assert_eq!(
5419            workspace.leader_for_pane(workspace.active_pane()),
5420            Some(client_b_id)
5421        );
5422    });
5423    workspace_b.read_with(cx_b, |workspace, _| {
5424        assert_eq!(
5425            workspace.leader_for_pane(workspace.active_pane()),
5426            Some(client_a_id)
5427        );
5428    });
5429}
5430
5431#[gpui::test(iterations = 100)]
5432async fn test_random_collaboration(
5433    cx: &mut TestAppContext,
5434    deterministic: Arc<Deterministic>,
5435    rng: StdRng,
5436) {
5437    deterministic.forbid_parking();
5438    let max_peers = env::var("MAX_PEERS")
5439        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
5440        .unwrap_or(5);
5441    assert!(max_peers <= 5);
5442
5443    let max_operations = env::var("OPERATIONS")
5444        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
5445        .unwrap_or(10);
5446
5447    let rng = Arc::new(Mutex::new(rng));
5448
5449    let guest_lang_registry = Arc::new(LanguageRegistry::test());
5450    let host_language_registry = Arc::new(LanguageRegistry::test());
5451
5452    let fs = FakeFs::new(cx.background());
5453    fs.insert_tree("/_collab", json!({"init": ""})).await;
5454
5455    let mut server = TestServer::start(cx.foreground(), cx.background()).await;
5456    let db = server.app_state.db.clone();
5457
5458    let room_creator_user_id = db
5459        .create_user(
5460            "room-creator@example.com",
5461            false,
5462            NewUserParams {
5463                github_login: "room-creator".into(),
5464                github_user_id: 0,
5465                invite_count: 0,
5466            },
5467        )
5468        .await
5469        .unwrap()
5470        .user_id;
5471    let mut available_guests = vec![
5472        "guest-1".to_string(),
5473        "guest-2".to_string(),
5474        "guest-3".to_string(),
5475        "guest-4".to_string(),
5476    ];
5477
5478    for (ix, username) in Some(&"host".to_string())
5479        .into_iter()
5480        .chain(&available_guests)
5481        .enumerate()
5482    {
5483        let user_id = db
5484            .create_user(
5485                &format!("{username}@example.com"),
5486                false,
5487                NewUserParams {
5488                    github_login: username.into(),
5489                    github_user_id: (ix + 1) as i32,
5490                    invite_count: 0,
5491                },
5492            )
5493            .await
5494            .unwrap()
5495            .user_id;
5496        server
5497            .app_state
5498            .db
5499            .send_contact_request(user_id, room_creator_user_id)
5500            .await
5501            .unwrap();
5502        server
5503            .app_state
5504            .db
5505            .respond_to_contact_request(room_creator_user_id, user_id, true)
5506            .await
5507            .unwrap();
5508    }
5509
5510    let _room_creator = server.create_client(cx, "room-creator").await;
5511    let active_call = cx.read(ActiveCall::global);
5512
5513    let mut clients = Vec::new();
5514    let mut user_ids = Vec::new();
5515    let mut op_start_signals = Vec::new();
5516
5517    let mut next_entity_id = 100000;
5518    let mut host_cx = TestAppContext::new(
5519        cx.foreground_platform(),
5520        cx.platform(),
5521        deterministic.build_foreground(next_entity_id),
5522        deterministic.build_background(),
5523        cx.font_cache(),
5524        cx.leak_detector(),
5525        next_entity_id,
5526        cx.function_name.clone(),
5527    );
5528    let host = server.create_client(&mut host_cx, "host").await;
5529    let host_project = host_cx.update(|cx| {
5530        Project::local(
5531            host.client.clone(),
5532            host.user_store.clone(),
5533            host.project_store.clone(),
5534            host_language_registry.clone(),
5535            fs.clone(),
5536            cx,
5537        )
5538    });
5539
5540    let (collab_worktree, _) = host_project
5541        .update(&mut host_cx, |project, cx| {
5542            project.find_or_create_local_worktree("/_collab", true, cx)
5543        })
5544        .await
5545        .unwrap();
5546    collab_worktree
5547        .read_with(&host_cx, |tree, _| tree.as_local().unwrap().scan_complete())
5548        .await;
5549
5550    // Set up fake language servers.
5551    let mut language = Language::new(
5552        LanguageConfig {
5553            name: "Rust".into(),
5554            path_suffixes: vec!["rs".to_string()],
5555            ..Default::default()
5556        },
5557        None,
5558    );
5559    let _fake_servers = language
5560        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5561            name: "the-fake-language-server",
5562            capabilities: lsp::LanguageServer::full_capabilities(),
5563            initializer: Some(Box::new({
5564                let rng = rng.clone();
5565                let fs = fs.clone();
5566                let project = host_project.downgrade();
5567                move |fake_server: &mut FakeLanguageServer| {
5568                    fake_server.handle_request::<lsp::request::Completion, _, _>(
5569                        |_, _| async move {
5570                            Ok(Some(lsp::CompletionResponse::Array(vec![
5571                                lsp::CompletionItem {
5572                                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
5573                                        range: lsp::Range::new(
5574                                            lsp::Position::new(0, 0),
5575                                            lsp::Position::new(0, 0),
5576                                        ),
5577                                        new_text: "the-new-text".to_string(),
5578                                    })),
5579                                    ..Default::default()
5580                                },
5581                            ])))
5582                        },
5583                    );
5584
5585                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
5586                        |_, _| async move {
5587                            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
5588                                lsp::CodeAction {
5589                                    title: "the-code-action".to_string(),
5590                                    ..Default::default()
5591                                },
5592                            )]))
5593                        },
5594                    );
5595
5596                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
5597                        |params, _| async move {
5598                            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
5599                                params.position,
5600                                params.position,
5601                            ))))
5602                        },
5603                    );
5604
5605                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
5606                        let fs = fs.clone();
5607                        let rng = rng.clone();
5608                        move |_, _| {
5609                            let fs = fs.clone();
5610                            let rng = rng.clone();
5611                            async move {
5612                                let files = fs.files().await;
5613                                let mut rng = rng.lock();
5614                                let count = rng.gen_range::<usize, _>(1..3);
5615                                let files = (0..count)
5616                                    .map(|_| files.choose(&mut *rng).unwrap())
5617                                    .collect::<Vec<_>>();
5618                                log::info!("LSP: Returning definitions in files {:?}", &files);
5619                                Ok(Some(lsp::GotoDefinitionResponse::Array(
5620                                    files
5621                                        .into_iter()
5622                                        .map(|file| lsp::Location {
5623                                            uri: lsp::Url::from_file_path(file).unwrap(),
5624                                            range: Default::default(),
5625                                        })
5626                                        .collect(),
5627                                )))
5628                            }
5629                        }
5630                    });
5631
5632                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
5633                        let rng = rng.clone();
5634                        let project = project;
5635                        move |params, mut cx| {
5636                            let highlights = if let Some(project) = project.upgrade(&cx) {
5637                                project.update(&mut cx, |project, cx| {
5638                                    let path = params
5639                                        .text_document_position_params
5640                                        .text_document
5641                                        .uri
5642                                        .to_file_path()
5643                                        .unwrap();
5644                                    let (worktree, relative_path) =
5645                                        project.find_local_worktree(&path, cx)?;
5646                                    let project_path =
5647                                        ProjectPath::from((worktree.read(cx).id(), relative_path));
5648                                    let buffer =
5649                                        project.get_open_buffer(&project_path, cx)?.read(cx);
5650
5651                                    let mut highlights = Vec::new();
5652                                    let highlight_count = rng.lock().gen_range(1..=5);
5653                                    let mut prev_end = 0;
5654                                    for _ in 0..highlight_count {
5655                                        let range =
5656                                            buffer.random_byte_range(prev_end, &mut *rng.lock());
5657
5658                                        highlights.push(lsp::DocumentHighlight {
5659                                            range: range_to_lsp(range.to_point_utf16(buffer)),
5660                                            kind: Some(lsp::DocumentHighlightKind::READ),
5661                                        });
5662                                        prev_end = range.end;
5663                                    }
5664                                    Some(highlights)
5665                                })
5666                            } else {
5667                                None
5668                            };
5669                            async move { Ok(highlights) }
5670                        }
5671                    });
5672                }
5673            })),
5674            ..Default::default()
5675        }))
5676        .await;
5677    host_language_registry.add(Arc::new(language));
5678
5679    let host_user_id = host.current_user_id(&host_cx);
5680    active_call
5681        .update(cx, |call, cx| {
5682            call.invite(host_user_id.to_proto(), None, cx)
5683        })
5684        .await
5685        .unwrap();
5686    active_call.read_with(cx, |call, cx| call.room().unwrap().read(cx).id());
5687    deterministic.run_until_parked();
5688    let host_active_call = host_cx.read(ActiveCall::global);
5689    host_active_call
5690        .update(&mut host_cx, |call, cx| call.accept_incoming(cx))
5691        .await
5692        .unwrap();
5693
5694    let host_project_id = host_active_call
5695        .update(&mut host_cx, |call, cx| {
5696            call.share_project(host_project.clone(), cx)
5697        })
5698        .await
5699        .unwrap();
5700
5701    let op_start_signal = futures::channel::mpsc::unbounded();
5702    user_ids.push(host_user_id);
5703    op_start_signals.push(op_start_signal.0);
5704    clients.push(host_cx.foreground().spawn(host.simulate_host(
5705        host_project,
5706        op_start_signal.1,
5707        rng.clone(),
5708        host_cx,
5709    )));
5710
5711    let disconnect_host_at = if rng.lock().gen_bool(0.2) {
5712        rng.lock().gen_range(0..max_operations)
5713    } else {
5714        max_operations
5715    };
5716
5717    let mut operations = 0;
5718    while operations < max_operations {
5719        if operations == disconnect_host_at {
5720            server.disconnect_client(user_ids[0]);
5721            deterministic.advance_clock(RECEIVE_TIMEOUT);
5722            drop(op_start_signals);
5723
5724            deterministic.start_waiting();
5725            let mut clients = futures::future::join_all(clients).await;
5726            deterministic.finish_waiting();
5727            deterministic.run_until_parked();
5728
5729            let (host, host_project, mut host_cx, host_err) = clients.remove(0);
5730            if let Some(host_err) = host_err {
5731                log::error!("host error - {:?}", host_err);
5732            }
5733            host_project.read_with(&host_cx, |project, _| assert!(!project.is_shared()));
5734            for (guest, guest_project, mut guest_cx, guest_err) in clients {
5735                if let Some(guest_err) = guest_err {
5736                    log::error!("{} error - {:?}", guest.username, guest_err);
5737                }
5738
5739                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
5740                guest_cx.update(|cx| {
5741                    cx.clear_globals();
5742                    drop((guest, guest_project));
5743                });
5744            }
5745            host_cx.update(|cx| {
5746                cx.clear_globals();
5747                drop((host, host_project));
5748            });
5749
5750            return;
5751        }
5752
5753        let distribution = rng.lock().gen_range(0..100);
5754        match distribution {
5755            0..=19 if !available_guests.is_empty() => {
5756                let guest_ix = rng.lock().gen_range(0..available_guests.len());
5757                let guest_username = available_guests.remove(guest_ix);
5758                log::info!("Adding new connection for {}", guest_username);
5759                next_entity_id += 100000;
5760                let mut guest_cx = TestAppContext::new(
5761                    cx.foreground_platform(),
5762                    cx.platform(),
5763                    deterministic.build_foreground(next_entity_id),
5764                    deterministic.build_background(),
5765                    cx.font_cache(),
5766                    cx.leak_detector(),
5767                    next_entity_id,
5768                    cx.function_name.clone(),
5769                );
5770
5771                deterministic.start_waiting();
5772                let guest = server.create_client(&mut guest_cx, &guest_username).await;
5773                let guest_user_id = guest.current_user_id(&guest_cx);
5774
5775                active_call
5776                    .update(cx, |call, cx| {
5777                        call.invite(guest_user_id.to_proto(), None, cx)
5778                    })
5779                    .await
5780                    .unwrap();
5781                deterministic.run_until_parked();
5782                guest_cx
5783                    .read(ActiveCall::global)
5784                    .update(&mut guest_cx, |call, cx| call.accept_incoming(cx))
5785                    .await
5786                    .unwrap();
5787
5788                let guest_project = Project::remote(
5789                    host_project_id,
5790                    guest.client.clone(),
5791                    guest.user_store.clone(),
5792                    guest.project_store.clone(),
5793                    guest_lang_registry.clone(),
5794                    FakeFs::new(cx.background()),
5795                    guest_cx.to_async(),
5796                )
5797                .await
5798                .unwrap();
5799                deterministic.finish_waiting();
5800
5801                let op_start_signal = futures::channel::mpsc::unbounded();
5802                user_ids.push(guest_user_id);
5803                op_start_signals.push(op_start_signal.0);
5804                clients.push(guest_cx.foreground().spawn(guest.simulate_guest(
5805                    guest_username.clone(),
5806                    guest_project,
5807                    op_start_signal.1,
5808                    rng.clone(),
5809                    guest_cx,
5810                )));
5811
5812                log::info!("Added connection for {}", guest_username);
5813                operations += 1;
5814            }
5815            20..=29 if clients.len() > 1 => {
5816                let guest_ix = rng.lock().gen_range(1..clients.len());
5817                log::info!("Removing guest {}", user_ids[guest_ix]);
5818                let removed_guest_id = user_ids.remove(guest_ix);
5819                let guest = clients.remove(guest_ix);
5820                op_start_signals.remove(guest_ix);
5821                server.forbid_connections();
5822                server.disconnect_client(removed_guest_id);
5823                deterministic.advance_clock(RECEIVE_TIMEOUT);
5824                deterministic.start_waiting();
5825                log::info!("Waiting for guest {} to exit...", removed_guest_id);
5826                let (guest, guest_project, mut guest_cx, guest_err) = guest.await;
5827                deterministic.finish_waiting();
5828                server.allow_connections();
5829
5830                if let Some(guest_err) = guest_err {
5831                    log::error!("{} error - {:?}", guest.username, guest_err);
5832                }
5833                guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
5834                for user_id in &user_ids {
5835                    let contacts = server.app_state.db.get_contacts(*user_id).await.unwrap();
5836                    let contacts = server
5837                        .store
5838                        .lock()
5839                        .await
5840                        .build_initial_contacts_update(contacts)
5841                        .contacts;
5842                    for contact in contacts {
5843                        if contact.online {
5844                            assert_ne!(
5845                                contact.user_id, removed_guest_id.0 as u64,
5846                                "removed guest is still a contact of another peer"
5847                            );
5848                        }
5849                    }
5850                }
5851
5852                log::info!("{} removed", guest.username);
5853                available_guests.push(guest.username.clone());
5854                guest_cx.update(|cx| {
5855                    cx.clear_globals();
5856                    drop((guest, guest_project));
5857                });
5858
5859                operations += 1;
5860            }
5861            _ => {
5862                while operations < max_operations && rng.lock().gen_bool(0.7) {
5863                    op_start_signals
5864                        .choose(&mut *rng.lock())
5865                        .unwrap()
5866                        .unbounded_send(())
5867                        .unwrap();
5868                    operations += 1;
5869                }
5870
5871                if rng.lock().gen_bool(0.8) {
5872                    deterministic.run_until_parked();
5873                }
5874            }
5875        }
5876    }
5877
5878    drop(op_start_signals);
5879    deterministic.start_waiting();
5880    let mut clients = futures::future::join_all(clients).await;
5881    deterministic.finish_waiting();
5882    deterministic.run_until_parked();
5883
5884    let (host_client, host_project, mut host_cx, host_err) = clients.remove(0);
5885    if let Some(host_err) = host_err {
5886        panic!("host error - {:?}", host_err);
5887    }
5888    let host_worktree_snapshots = host_project.read_with(&host_cx, |project, cx| {
5889        project
5890            .worktrees(cx)
5891            .map(|worktree| {
5892                let snapshot = worktree.read(cx).snapshot();
5893                (snapshot.id(), snapshot)
5894            })
5895            .collect::<BTreeMap<_, _>>()
5896    });
5897
5898    host_project.read_with(&host_cx, |project, cx| project.check_invariants(cx));
5899
5900    for (guest_client, guest_project, mut guest_cx, guest_err) in clients.into_iter() {
5901        if let Some(guest_err) = guest_err {
5902            panic!("{} error - {:?}", guest_client.username, guest_err);
5903        }
5904        let worktree_snapshots = guest_project.read_with(&guest_cx, |project, cx| {
5905            project
5906                .worktrees(cx)
5907                .map(|worktree| {
5908                    let worktree = worktree.read(cx);
5909                    (worktree.id(), worktree.snapshot())
5910                })
5911                .collect::<BTreeMap<_, _>>()
5912        });
5913
5914        assert_eq!(
5915            worktree_snapshots.keys().collect::<Vec<_>>(),
5916            host_worktree_snapshots.keys().collect::<Vec<_>>(),
5917            "{} has different worktrees than the host",
5918            guest_client.username
5919        );
5920        for (id, host_snapshot) in &host_worktree_snapshots {
5921            let guest_snapshot = &worktree_snapshots[id];
5922            assert_eq!(
5923                guest_snapshot.root_name(),
5924                host_snapshot.root_name(),
5925                "{} has different root name than the host for worktree {}",
5926                guest_client.username,
5927                id
5928            );
5929            assert_eq!(
5930                guest_snapshot.entries(false).collect::<Vec<_>>(),
5931                host_snapshot.entries(false).collect::<Vec<_>>(),
5932                "{} has different snapshot than the host for worktree {}",
5933                guest_client.username,
5934                id
5935            );
5936            assert_eq!(guest_snapshot.scan_id(), host_snapshot.scan_id());
5937        }
5938
5939        guest_project.read_with(&guest_cx, |project, cx| project.check_invariants(cx));
5940
5941        for guest_buffer in &guest_client.buffers {
5942            let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
5943            let host_buffer = host_project.read_with(&host_cx, |project, cx| {
5944                project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
5945                    panic!(
5946                        "host does not have buffer for guest:{}, peer:{}, id:{}",
5947                        guest_client.username, guest_client.peer_id, buffer_id
5948                    )
5949                })
5950            });
5951            let path =
5952                host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
5953
5954            assert_eq!(
5955                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
5956                0,
5957                "{}, buffer {}, path {:?} has deferred operations",
5958                guest_client.username,
5959                buffer_id,
5960                path,
5961            );
5962            assert_eq!(
5963                guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
5964                host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
5965                "{}, buffer {}, path {:?}, differs from the host's buffer",
5966                guest_client.username,
5967                buffer_id,
5968                path
5969            );
5970        }
5971
5972        guest_cx.update(|cx| {
5973            cx.clear_globals();
5974            drop((guest_project, guest_client));
5975        });
5976    }
5977
5978    host_cx.update(|cx| {
5979        cx.clear_globals();
5980        drop((host_client, host_project))
5981    });
5982}
5983
5984struct TestServer {
5985    peer: Arc<Peer>,
5986    app_state: Arc<AppState>,
5987    server: Arc<Server>,
5988    foreground: Rc<executor::Foreground>,
5989    notifications: mpsc::UnboundedReceiver<()>,
5990    connection_killers: Arc<Mutex<HashMap<UserId, Arc<AtomicBool>>>>,
5991    forbid_connections: Arc<AtomicBool>,
5992    _test_db: TestDb,
5993}
5994
5995impl TestServer {
5996    async fn start(
5997        foreground: Rc<executor::Foreground>,
5998        background: Arc<executor::Background>,
5999    ) -> Self {
6000        let test_db = TestDb::fake(background.clone());
6001        let app_state = Self::build_app_state(&test_db).await;
6002        let peer = Peer::new();
6003        let notifications = mpsc::unbounded();
6004        let server = Server::new(app_state.clone(), Some(notifications.0));
6005        Self {
6006            peer,
6007            app_state,
6008            server,
6009            foreground,
6010            notifications: notifications.1,
6011            connection_killers: Default::default(),
6012            forbid_connections: Default::default(),
6013            _test_db: test_db,
6014        }
6015    }
6016
6017    async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
6018        cx.update(|cx| {
6019            let mut settings = Settings::test(cx);
6020            settings.projects_online_by_default = false;
6021            cx.set_global(settings);
6022        });
6023
6024        let http = FakeHttpClient::with_404_response();
6025        let user_id = if let Ok(Some(user)) = self
6026            .app_state
6027            .db
6028            .get_user_by_github_account(name, None)
6029            .await
6030        {
6031            user.id
6032        } else {
6033            self.app_state
6034                .db
6035                .create_user(
6036                    &format!("{name}@example.com"),
6037                    false,
6038                    NewUserParams {
6039                        github_login: name.into(),
6040                        github_user_id: 0,
6041                        invite_count: 0,
6042                    },
6043                )
6044                .await
6045                .unwrap()
6046                .user_id
6047        };
6048        let client_name = name.to_string();
6049        let mut client = cx.read(|cx| Client::new(http.clone(), cx));
6050        let server = self.server.clone();
6051        let db = self.app_state.db.clone();
6052        let connection_killers = self.connection_killers.clone();
6053        let forbid_connections = self.forbid_connections.clone();
6054        let (connection_id_tx, mut connection_id_rx) = mpsc::channel(16);
6055
6056        Arc::get_mut(&mut client)
6057            .unwrap()
6058            .set_id(user_id.0 as usize)
6059            .override_authenticate(move |cx| {
6060                cx.spawn(|_| async move {
6061                    let access_token = "the-token".to_string();
6062                    Ok(Credentials {
6063                        user_id: user_id.0 as u64,
6064                        access_token,
6065                    })
6066                })
6067            })
6068            .override_establish_connection(move |credentials, cx| {
6069                assert_eq!(credentials.user_id, user_id.0 as u64);
6070                assert_eq!(credentials.access_token, "the-token");
6071
6072                let server = server.clone();
6073                let db = db.clone();
6074                let connection_killers = connection_killers.clone();
6075                let forbid_connections = forbid_connections.clone();
6076                let client_name = client_name.clone();
6077                let connection_id_tx = connection_id_tx.clone();
6078                cx.spawn(move |cx| async move {
6079                    if forbid_connections.load(SeqCst) {
6080                        Err(EstablishConnectionError::other(anyhow!(
6081                            "server is forbidding connections"
6082                        )))
6083                    } else {
6084                        let (client_conn, server_conn, killed) =
6085                            Connection::in_memory(cx.background());
6086                        connection_killers.lock().insert(user_id, killed);
6087                        let user = db.get_user_by_id(user_id).await.unwrap().unwrap();
6088                        cx.background()
6089                            .spawn(server.handle_connection(
6090                                server_conn,
6091                                client_name,
6092                                user,
6093                                Some(connection_id_tx),
6094                                cx.background(),
6095                            ))
6096                            .detach();
6097                        Ok(client_conn)
6098                    }
6099                })
6100            });
6101
6102        let fs = FakeFs::new(cx.background());
6103        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));
6104        let project_store = cx.add_model(|_| ProjectStore::new());
6105        let app_state = Arc::new(workspace::AppState {
6106            client: client.clone(),
6107            user_store: user_store.clone(),
6108            project_store: project_store.clone(),
6109            languages: Arc::new(LanguageRegistry::new(Task::ready(()))),
6110            themes: ThemeRegistry::new((), cx.font_cache()),
6111            fs: fs.clone(),
6112            build_window_options: Default::default,
6113            initialize_workspace: |_, _, _| unimplemented!(),
6114            default_item_factory: |_, _| unimplemented!(),
6115        });
6116
6117        Channel::init(&client);
6118        Project::init(&client);
6119        cx.update(|cx| {
6120            workspace::init(app_state.clone(), cx);
6121            call::init(client.clone(), user_store.clone(), cx);
6122        });
6123
6124        client
6125            .authenticate_and_connect(false, &cx.to_async())
6126            .await
6127            .unwrap();
6128        let peer_id = PeerId(connection_id_rx.next().await.unwrap().0);
6129
6130        let client = TestClient {
6131            client,
6132            peer_id,
6133            username: name.to_string(),
6134            user_store,
6135            project_store,
6136            fs,
6137            language_registry: Arc::new(LanguageRegistry::test()),
6138            buffers: Default::default(),
6139        };
6140        client.wait_for_current_user(cx).await;
6141        client
6142    }
6143
6144    fn disconnect_client(&self, user_id: UserId) {
6145        self.connection_killers
6146            .lock()
6147            .remove(&user_id)
6148            .unwrap()
6149            .store(true, SeqCst);
6150    }
6151
6152    fn forbid_connections(&self) {
6153        self.forbid_connections.store(true, SeqCst);
6154    }
6155
6156    fn allow_connections(&self) {
6157        self.forbid_connections.store(false, SeqCst);
6158    }
6159
6160    async fn make_contacts(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6161        for ix in 1..clients.len() {
6162            let (left, right) = clients.split_at_mut(ix);
6163            let (client_a, cx_a) = left.last_mut().unwrap();
6164            for (client_b, cx_b) in right {
6165                client_a
6166                    .user_store
6167                    .update(*cx_a, |store, cx| {
6168                        store.request_contact(client_b.user_id().unwrap(), cx)
6169                    })
6170                    .await
6171                    .unwrap();
6172                cx_a.foreground().run_until_parked();
6173                client_b
6174                    .user_store
6175                    .update(*cx_b, |store, cx| {
6176                        store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6177                    })
6178                    .await
6179                    .unwrap();
6180            }
6181        }
6182    }
6183
6184    async fn create_room(&self, clients: &mut [(&TestClient, &mut TestAppContext)]) {
6185        self.make_contacts(clients).await;
6186
6187        let (left, right) = clients.split_at_mut(1);
6188        let (_client_a, cx_a) = &mut left[0];
6189        let active_call_a = cx_a.read(ActiveCall::global);
6190
6191        for (client_b, cx_b) in right {
6192            let user_id_b = client_b.current_user_id(*cx_b).to_proto();
6193            active_call_a
6194                .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx))
6195                .await
6196                .unwrap();
6197
6198            cx_b.foreground().run_until_parked();
6199            let active_call_b = cx_b.read(ActiveCall::global);
6200            active_call_b
6201                .update(*cx_b, |call, cx| call.accept_incoming(cx))
6202                .await
6203                .unwrap();
6204        }
6205    }
6206
6207    async fn build_app_state(test_db: &TestDb) -> Arc<AppState> {
6208        Arc::new(AppState {
6209            db: test_db.db().clone(),
6210            api_token: Default::default(),
6211            invite_link_prefix: Default::default(),
6212        })
6213    }
6214
6215    async fn condition<F>(&mut self, mut predicate: F)
6216    where
6217        F: FnMut(&Store) -> bool,
6218    {
6219        assert!(
6220            self.foreground.parking_forbidden(),
6221            "you must call forbid_parking to use server conditions so we don't block indefinitely"
6222        );
6223        while !(predicate)(&*self.server.store.lock().await) {
6224            self.foreground.start_waiting();
6225            self.notifications.next().await;
6226            self.foreground.finish_waiting();
6227        }
6228    }
6229}
6230
6231impl Deref for TestServer {
6232    type Target = Server;
6233
6234    fn deref(&self) -> &Self::Target {
6235        &self.server
6236    }
6237}
6238
6239impl Drop for TestServer {
6240    fn drop(&mut self) {
6241        self.peer.reset();
6242    }
6243}
6244
6245struct TestClient {
6246    client: Arc<Client>,
6247    username: String,
6248    pub peer_id: PeerId,
6249    pub user_store: ModelHandle<UserStore>,
6250    pub project_store: ModelHandle<ProjectStore>,
6251    language_registry: Arc<LanguageRegistry>,
6252    fs: Arc<FakeFs>,
6253    buffers: HashSet<ModelHandle<language::Buffer>>,
6254}
6255
6256impl Deref for TestClient {
6257    type Target = Arc<Client>;
6258
6259    fn deref(&self) -> &Self::Target {
6260        &self.client
6261    }
6262}
6263
6264struct ContactsSummary {
6265    pub current: Vec<String>,
6266    pub outgoing_requests: Vec<String>,
6267    pub incoming_requests: Vec<String>,
6268}
6269
6270impl TestClient {
6271    pub fn current_user_id(&self, cx: &TestAppContext) -> UserId {
6272        UserId::from_proto(
6273            self.user_store
6274                .read_with(cx, |user_store, _| user_store.current_user().unwrap().id),
6275        )
6276    }
6277
6278    async fn wait_for_current_user(&self, cx: &TestAppContext) {
6279        let mut authed_user = self
6280            .user_store
6281            .read_with(cx, |user_store, _| user_store.watch_current_user());
6282        while authed_user.next().await.unwrap().is_none() {}
6283    }
6284
6285    async fn clear_contacts(&self, cx: &mut TestAppContext) {
6286        self.user_store
6287            .update(cx, |store, _| store.clear_contacts())
6288            .await;
6289    }
6290
6291    fn summarize_contacts(&self, cx: &TestAppContext) -> ContactsSummary {
6292        self.user_store.read_with(cx, |store, _| ContactsSummary {
6293            current: store
6294                .contacts()
6295                .iter()
6296                .map(|contact| contact.user.github_login.clone())
6297                .collect(),
6298            outgoing_requests: store
6299                .outgoing_contact_requests()
6300                .iter()
6301                .map(|user| user.github_login.clone())
6302                .collect(),
6303            incoming_requests: store
6304                .incoming_contact_requests()
6305                .iter()
6306                .map(|user| user.github_login.clone())
6307                .collect(),
6308        })
6309    }
6310
6311    async fn build_local_project(
6312        &self,
6313        root_path: impl AsRef<Path>,
6314        cx: &mut TestAppContext,
6315    ) -> (ModelHandle<Project>, WorktreeId) {
6316        let project = cx.update(|cx| {
6317            Project::local(
6318                self.client.clone(),
6319                self.user_store.clone(),
6320                self.project_store.clone(),
6321                self.language_registry.clone(),
6322                self.fs.clone(),
6323                cx,
6324            )
6325        });
6326        let (worktree, _) = project
6327            .update(cx, |p, cx| {
6328                p.find_or_create_local_worktree(root_path, true, cx)
6329            })
6330            .await
6331            .unwrap();
6332        worktree
6333            .read_with(cx, |tree, _| tree.as_local().unwrap().scan_complete())
6334            .await;
6335        (project, worktree.read_with(cx, |tree, _| tree.id()))
6336    }
6337
6338    async fn build_remote_project(
6339        &self,
6340        host_project_id: u64,
6341        guest_cx: &mut TestAppContext,
6342    ) -> ModelHandle<Project> {
6343        let project_b = guest_cx.spawn(|cx| {
6344            Project::remote(
6345                host_project_id,
6346                self.client.clone(),
6347                self.user_store.clone(),
6348                self.project_store.clone(),
6349                self.language_registry.clone(),
6350                FakeFs::new(cx.background()),
6351                cx,
6352            )
6353        });
6354        project_b.await.unwrap()
6355    }
6356
6357    fn build_workspace(
6358        &self,
6359        project: &ModelHandle<Project>,
6360        cx: &mut TestAppContext,
6361    ) -> ViewHandle<Workspace> {
6362        let (_, root_view) = cx.add_window(|_| EmptyView);
6363        cx.add_view(&root_view, |cx| {
6364            Workspace::new(project.clone(), |_, _| unimplemented!(), cx)
6365        })
6366    }
6367
6368    async fn simulate_host(
6369        mut self,
6370        project: ModelHandle<Project>,
6371        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6372        rng: Arc<Mutex<StdRng>>,
6373        mut cx: TestAppContext,
6374    ) -> (
6375        Self,
6376        ModelHandle<Project>,
6377        TestAppContext,
6378        Option<anyhow::Error>,
6379    ) {
6380        async fn simulate_host_internal(
6381            client: &mut TestClient,
6382            project: ModelHandle<Project>,
6383            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6384            rng: Arc<Mutex<StdRng>>,
6385            cx: &mut TestAppContext,
6386        ) -> anyhow::Result<()> {
6387            let fs = project.read_with(cx, |project, _| project.fs().clone());
6388
6389            while op_start_signal.next().await.is_some() {
6390                let distribution = rng.lock().gen_range::<usize, _>(0..100);
6391                let files = fs.as_fake().files().await;
6392                match distribution {
6393                    0..=19 if !files.is_empty() => {
6394                        let path = files.choose(&mut *rng.lock()).unwrap();
6395                        let mut path = path.as_path();
6396                        while let Some(parent_path) = path.parent() {
6397                            path = parent_path;
6398                            if rng.lock().gen() {
6399                                break;
6400                            }
6401                        }
6402
6403                        log::info!("Host: find/create local worktree {:?}", path);
6404                        let find_or_create_worktree = project.update(cx, |project, cx| {
6405                            project.find_or_create_local_worktree(path, true, cx)
6406                        });
6407                        if rng.lock().gen() {
6408                            cx.background().spawn(find_or_create_worktree).detach();
6409                        } else {
6410                            find_or_create_worktree.await?;
6411                        }
6412                    }
6413                    20..=79 if !files.is_empty() => {
6414                        let buffer = if client.buffers.is_empty() || rng.lock().gen() {
6415                            let file = files.choose(&mut *rng.lock()).unwrap();
6416                            let (worktree, path) = project
6417                                .update(cx, |project, cx| {
6418                                    project.find_or_create_local_worktree(file.clone(), true, cx)
6419                                })
6420                                .await?;
6421                            let project_path =
6422                                worktree.read_with(cx, |worktree, _| (worktree.id(), path));
6423                            log::info!(
6424                                "Host: opening path {:?}, worktree {}, relative_path {:?}",
6425                                file,
6426                                project_path.0,
6427                                project_path.1
6428                            );
6429                            let buffer = project
6430                                .update(cx, |project, cx| project.open_buffer(project_path, cx))
6431                                .await
6432                                .unwrap();
6433                            client.buffers.insert(buffer.clone());
6434                            buffer
6435                        } else {
6436                            client
6437                                .buffers
6438                                .iter()
6439                                .choose(&mut *rng.lock())
6440                                .unwrap()
6441                                .clone()
6442                        };
6443
6444                        if rng.lock().gen_bool(0.1) {
6445                            cx.update(|cx| {
6446                                log::info!(
6447                                    "Host: dropping buffer {:?}",
6448                                    buffer.read(cx).file().unwrap().full_path(cx)
6449                                );
6450                                client.buffers.remove(&buffer);
6451                                drop(buffer);
6452                            });
6453                        } else {
6454                            buffer.update(cx, |buffer, cx| {
6455                                log::info!(
6456                                    "Host: updating buffer {:?} ({})",
6457                                    buffer.file().unwrap().full_path(cx),
6458                                    buffer.remote_id()
6459                                );
6460
6461                                if rng.lock().gen_bool(0.7) {
6462                                    buffer.randomly_edit(&mut *rng.lock(), 5, cx);
6463                                } else {
6464                                    buffer.randomly_undo_redo(&mut *rng.lock(), cx);
6465                                }
6466                            });
6467                        }
6468                    }
6469                    _ => loop {
6470                        let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
6471                        let mut path = PathBuf::new();
6472                        path.push("/");
6473                        for _ in 0..path_component_count {
6474                            let letter = rng.lock().gen_range(b'a'..=b'z');
6475                            path.push(std::str::from_utf8(&[letter]).unwrap());
6476                        }
6477                        path.set_extension("rs");
6478                        let parent_path = path.parent().unwrap();
6479
6480                        log::info!("Host: creating file {:?}", path,);
6481
6482                        if fs.create_dir(parent_path).await.is_ok()
6483                            && fs.create_file(&path, Default::default()).await.is_ok()
6484                        {
6485                            break;
6486                        } else {
6487                            log::info!("Host: cannot create file");
6488                        }
6489                    },
6490                }
6491
6492                cx.background().simulate_random_delay().await;
6493            }
6494
6495            Ok(())
6496        }
6497
6498        let result =
6499            simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
6500        log::info!("Host done");
6501        (self, project, cx, result.err())
6502    }
6503
6504    pub async fn simulate_guest(
6505        mut self,
6506        guest_username: String,
6507        project: ModelHandle<Project>,
6508        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6509        rng: Arc<Mutex<StdRng>>,
6510        mut cx: TestAppContext,
6511    ) -> (
6512        Self,
6513        ModelHandle<Project>,
6514        TestAppContext,
6515        Option<anyhow::Error>,
6516    ) {
6517        async fn simulate_guest_internal(
6518            client: &mut TestClient,
6519            guest_username: &str,
6520            project: ModelHandle<Project>,
6521            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
6522            rng: Arc<Mutex<StdRng>>,
6523            cx: &mut TestAppContext,
6524        ) -> anyhow::Result<()> {
6525            while op_start_signal.next().await.is_some() {
6526                let buffer = if client.buffers.is_empty() || rng.lock().gen() {
6527                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
6528                        project
6529                            .worktrees(cx)
6530                            .filter(|worktree| {
6531                                let worktree = worktree.read(cx);
6532                                worktree.is_visible()
6533                                    && worktree.entries(false).any(|e| e.is_file())
6534                            })
6535                            .choose(&mut *rng.lock())
6536                    }) {
6537                        worktree
6538                    } else {
6539                        cx.background().simulate_random_delay().await;
6540                        continue;
6541                    };
6542
6543                    let (worktree_root_name, project_path) =
6544                        worktree.read_with(cx, |worktree, _| {
6545                            let entry = worktree
6546                                .entries(false)
6547                                .filter(|e| e.is_file())
6548                                .choose(&mut *rng.lock())
6549                                .unwrap();
6550                            (
6551                                worktree.root_name().to_string(),
6552                                (worktree.id(), entry.path.clone()),
6553                            )
6554                        });
6555                    log::info!(
6556                        "{}: opening path {:?} in worktree {} ({})",
6557                        guest_username,
6558                        project_path.1,
6559                        project_path.0,
6560                        worktree_root_name,
6561                    );
6562                    let buffer = project
6563                        .update(cx, |project, cx| {
6564                            project.open_buffer(project_path.clone(), cx)
6565                        })
6566                        .await?;
6567                    log::info!(
6568                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
6569                        guest_username,
6570                        project_path.1,
6571                        project_path.0,
6572                        worktree_root_name,
6573                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
6574                    );
6575                    client.buffers.insert(buffer.clone());
6576                    buffer
6577                } else {
6578                    client
6579                        .buffers
6580                        .iter()
6581                        .choose(&mut *rng.lock())
6582                        .unwrap()
6583                        .clone()
6584                };
6585
6586                let choice = rng.lock().gen_range(0..100);
6587                match choice {
6588                    0..=9 => {
6589                        cx.update(|cx| {
6590                            log::info!(
6591                                "{}: dropping buffer {:?}",
6592                                guest_username,
6593                                buffer.read(cx).file().unwrap().full_path(cx)
6594                            );
6595                            client.buffers.remove(&buffer);
6596                            drop(buffer);
6597                        });
6598                    }
6599                    10..=19 => {
6600                        let completions = project.update(cx, |project, cx| {
6601                            log::info!(
6602                                "{}: requesting completions for buffer {} ({:?})",
6603                                guest_username,
6604                                buffer.read(cx).remote_id(),
6605                                buffer.read(cx).file().unwrap().full_path(cx)
6606                            );
6607                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
6608                            project.completions(&buffer, offset, cx)
6609                        });
6610                        let completions = cx.background().spawn(async move {
6611                            completions
6612                                .await
6613                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
6614                        });
6615                        if rng.lock().gen_bool(0.3) {
6616                            log::info!("{}: detaching completions request", guest_username);
6617                            cx.update(|cx| completions.detach_and_log_err(cx));
6618                        } else {
6619                            completions.await?;
6620                        }
6621                    }
6622                    20..=29 => {
6623                        let code_actions = project.update(cx, |project, cx| {
6624                            log::info!(
6625                                "{}: requesting code actions for buffer {} ({:?})",
6626                                guest_username,
6627                                buffer.read(cx).remote_id(),
6628                                buffer.read(cx).file().unwrap().full_path(cx)
6629                            );
6630                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
6631                            project.code_actions(&buffer, range, cx)
6632                        });
6633                        let code_actions = cx.background().spawn(async move {
6634                            code_actions
6635                                .await
6636                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
6637                        });
6638                        if rng.lock().gen_bool(0.3) {
6639                            log::info!("{}: detaching code actions request", guest_username);
6640                            cx.update(|cx| code_actions.detach_and_log_err(cx));
6641                        } else {
6642                            code_actions.await?;
6643                        }
6644                    }
6645                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
6646                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
6647                            log::info!(
6648                                "{}: saving buffer {} ({:?})",
6649                                guest_username,
6650                                buffer.remote_id(),
6651                                buffer.file().unwrap().full_path(cx)
6652                            );
6653                            (buffer.version(), buffer.save(cx))
6654                        });
6655                        let save = cx.background().spawn(async move {
6656                            let (saved_version, _, _) = save
6657                                .await
6658                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
6659                            assert!(saved_version.observed_all(&requested_version));
6660                            Ok::<_, anyhow::Error>(())
6661                        });
6662                        if rng.lock().gen_bool(0.3) {
6663                            log::info!("{}: detaching save request", guest_username);
6664                            cx.update(|cx| save.detach_and_log_err(cx));
6665                        } else {
6666                            save.await?;
6667                        }
6668                    }
6669                    40..=44 => {
6670                        let prepare_rename = project.update(cx, |project, cx| {
6671                            log::info!(
6672                                "{}: preparing rename for buffer {} ({:?})",
6673                                guest_username,
6674                                buffer.read(cx).remote_id(),
6675                                buffer.read(cx).file().unwrap().full_path(cx)
6676                            );
6677                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
6678                            project.prepare_rename(buffer, offset, cx)
6679                        });
6680                        let prepare_rename = cx.background().spawn(async move {
6681                            prepare_rename
6682                                .await
6683                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
6684                        });
6685                        if rng.lock().gen_bool(0.3) {
6686                            log::info!("{}: detaching prepare rename request", guest_username);
6687                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
6688                        } else {
6689                            prepare_rename.await?;
6690                        }
6691                    }
6692                    45..=49 => {
6693                        let definitions = project.update(cx, |project, cx| {
6694                            log::info!(
6695                                "{}: requesting definitions for buffer {} ({:?})",
6696                                guest_username,
6697                                buffer.read(cx).remote_id(),
6698                                buffer.read(cx).file().unwrap().full_path(cx)
6699                            );
6700                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
6701                            project.definition(&buffer, offset, cx)
6702                        });
6703                        let definitions = cx.background().spawn(async move {
6704                            definitions
6705                                .await
6706                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
6707                        });
6708                        if rng.lock().gen_bool(0.3) {
6709                            log::info!("{}: detaching definitions request", guest_username);
6710                            cx.update(|cx| definitions.detach_and_log_err(cx));
6711                        } else {
6712                            client.buffers.extend(
6713                                definitions.await?.into_iter().map(|loc| loc.target.buffer),
6714                            );
6715                        }
6716                    }
6717                    50..=54 => {
6718                        let highlights = project.update(cx, |project, cx| {
6719                            log::info!(
6720                                "{}: requesting highlights for buffer {} ({:?})",
6721                                guest_username,
6722                                buffer.read(cx).remote_id(),
6723                                buffer.read(cx).file().unwrap().full_path(cx)
6724                            );
6725                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
6726                            project.document_highlights(&buffer, offset, cx)
6727                        });
6728                        let highlights = cx.background().spawn(async move {
6729                            highlights
6730                                .await
6731                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
6732                        });
6733                        if rng.lock().gen_bool(0.3) {
6734                            log::info!("{}: detaching highlights request", guest_username);
6735                            cx.update(|cx| highlights.detach_and_log_err(cx));
6736                        } else {
6737                            highlights.await?;
6738                        }
6739                    }
6740                    55..=59 => {
6741                        let search = project.update(cx, |project, cx| {
6742                            let query = rng.lock().gen_range('a'..='z');
6743                            log::info!("{}: project-wide search {:?}", guest_username, query);
6744                            project.search(SearchQuery::text(query, false, false), cx)
6745                        });
6746                        let search = cx.background().spawn(async move {
6747                            search
6748                                .await
6749                                .map_err(|err| anyhow!("search request failed: {:?}", err))
6750                        });
6751                        if rng.lock().gen_bool(0.3) {
6752                            log::info!("{}: detaching search request", guest_username);
6753                            cx.update(|cx| search.detach_and_log_err(cx));
6754                        } else {
6755                            client.buffers.extend(search.await?.into_keys());
6756                        }
6757                    }
6758                    60..=69 => {
6759                        let worktree = project
6760                            .read_with(cx, |project, cx| {
6761                                project
6762                                    .worktrees(cx)
6763                                    .filter(|worktree| {
6764                                        let worktree = worktree.read(cx);
6765                                        worktree.is_visible()
6766                                            && worktree.entries(false).any(|e| e.is_file())
6767                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
6768                                    })
6769                                    .choose(&mut *rng.lock())
6770                            })
6771                            .unwrap();
6772                        let (worktree_id, worktree_root_name) = worktree
6773                            .read_with(cx, |worktree, _| {
6774                                (worktree.id(), worktree.root_name().to_string())
6775                            });
6776
6777                        let mut new_name = String::new();
6778                        for _ in 0..10 {
6779                            let letter = rng.lock().gen_range('a'..='z');
6780                            new_name.push(letter);
6781                        }
6782                        let mut new_path = PathBuf::new();
6783                        new_path.push(new_name);
6784                        new_path.set_extension("rs");
6785                        log::info!(
6786                            "{}: creating {:?} in worktree {} ({})",
6787                            guest_username,
6788                            new_path,
6789                            worktree_id,
6790                            worktree_root_name,
6791                        );
6792                        project
6793                            .update(cx, |project, cx| {
6794                                project.create_entry((worktree_id, new_path), false, cx)
6795                            })
6796                            .unwrap()
6797                            .await?;
6798                    }
6799                    _ => {
6800                        buffer.update(cx, |buffer, cx| {
6801                            log::info!(
6802                                "{}: updating buffer {} ({:?})",
6803                                guest_username,
6804                                buffer.remote_id(),
6805                                buffer.file().unwrap().full_path(cx)
6806                            );
6807                            if rng.lock().gen_bool(0.7) {
6808                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
6809                            } else {
6810                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
6811                            }
6812                        });
6813                    }
6814                }
6815                cx.background().simulate_random_delay().await;
6816            }
6817            Ok(())
6818        }
6819
6820        let result = simulate_guest_internal(
6821            &mut self,
6822            &guest_username,
6823            project.clone(),
6824            op_start_signal,
6825            rng,
6826            &mut cx,
6827        )
6828        .await;
6829        log::info!("{}: done", guest_username);
6830
6831        (self, project, cx, result.err())
6832    }
6833}
6834
6835impl Drop for TestClient {
6836    fn drop(&mut self) {
6837        self.client.tear_down();
6838    }
6839}
6840
6841impl Executor for Arc<gpui::executor::Background> {
6842    type Sleep = gpui::executor::Timer;
6843
6844    fn spawn_detached<F: 'static + Send + Future<Output = ()>>(&self, future: F) {
6845        self.spawn(future).detach();
6846    }
6847
6848    fn sleep(&self, duration: Duration) -> Self::Sleep {
6849        self.as_ref().timer(duration)
6850    }
6851}
6852
6853fn channel_messages(channel: &Channel) -> Vec<(String, String, bool)> {
6854    channel
6855        .messages()
6856        .cursor::<()>()
6857        .map(|m| {
6858            (
6859                m.sender.github_login.clone(),
6860                m.body.clone(),
6861                m.is_pending(),
6862            )
6863        })
6864        .collect()
6865}
6866
6867#[derive(Debug, Eq, PartialEq)]
6868struct RoomParticipants {
6869    remote: Vec<String>,
6870    pending: Vec<String>,
6871}
6872
6873fn room_participants(room: &ModelHandle<Room>, cx: &mut TestAppContext) -> RoomParticipants {
6874    room.read_with(cx, |room, _| RoomParticipants {
6875        remote: room
6876            .remote_participants()
6877            .iter()
6878            .map(|(_, participant)| participant.user.github_login.clone())
6879            .collect(),
6880        pending: room
6881            .pending_participants()
6882            .iter()
6883            .map(|user| user.github_login.clone())
6884            .collect(),
6885    })
6886}