integration_tests.rs

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