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