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