integration_tests.rs

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