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