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