integration_tests.rs

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