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