integration_tests.rs

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