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