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