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