integration_tests.rs

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