integration_tests.rs

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