integration_tests.rs

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