integration_tests.rs

   1use crate::{
   2    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
   3    tests::{
   4        RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
   5        room_participants, rust_lang,
   6    },
   7};
   8use anyhow::{Result, anyhow};
   9use assistant_context_editor::ContextStore;
  10use assistant_slash_command::SlashCommandWorkingSet;
  11use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
  12use call::{ActiveCall, ParticipantLocation, Room, room};
  13use client::{RECEIVE_TIMEOUT, User};
  14use collections::{HashMap, HashSet};
  15use fs::{FakeFs, Fs as _, RemoveOptions};
  16use futures::{StreamExt as _, channel::mpsc};
  17use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
  18use gpui::{
  19    App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext,
  20    UpdateGlobal, px, size,
  21};
  22use language::{
  23    Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
  24    LineEnding, OffsetRangeExt, Point, Rope,
  25    language_settings::{
  26        AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
  27    },
  28    tree_sitter_rust, tree_sitter_typescript,
  29};
  30use lsp::{LanguageServerId, OneOf};
  31use parking_lot::Mutex;
  32use pretty_assertions::assert_eq;
  33use project::{
  34    DiagnosticSummary, HoverBlockKind, Project, ProjectPath,
  35    lsp_store::{FormatTrigger, LspFormatTarget},
  36    search::{SearchQuery, SearchResult},
  37};
  38use prompt_store::PromptBuilder;
  39use rand::prelude::*;
  40use serde_json::json;
  41use settings::SettingsStore;
  42use std::{
  43    cell::{Cell, RefCell},
  44    env, future, mem,
  45    path::{Path, PathBuf},
  46    rc::Rc,
  47    sync::{
  48        Arc,
  49        atomic::{AtomicBool, Ordering::SeqCst},
  50    },
  51    time::Duration,
  52};
  53use unindent::Unindent as _;
  54use util::{path, separator, uri};
  55use workspace::Pane;
  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_database_failure_during_client_reconnection(
  66    executor: BackgroundExecutor,
  67    cx: &mut TestAppContext,
  68) {
  69    let mut server = TestServer::start(executor.clone()).await;
  70    let client = server.create_client(cx, "user_a").await;
  71
  72    // Keep disconnecting the client until a database failure prevents it from
  73    // reconnecting.
  74    server.test_db.set_query_failure_probability(0.3);
  75    loop {
  76        server.disconnect_client(client.peer_id().unwrap());
  77        executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  78        if !client.status().borrow().is_connected() {
  79            break;
  80        }
  81    }
  82
  83    // Make the database healthy again and ensure the client can finally connect.
  84    server.test_db.set_query_failure_probability(0.);
  85    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  86    assert!(
  87        matches!(*client.status().borrow(), client::Status::Connected { .. }),
  88        "status was {:?}",
  89        *client.status().borrow()
  90    );
  91}
  92
  93#[gpui::test(iterations = 10)]
  94async fn test_basic_calls(
  95    executor: BackgroundExecutor,
  96    cx_a: &mut TestAppContext,
  97    cx_b: &mut TestAppContext,
  98    cx_b2: &mut TestAppContext,
  99    cx_c: &mut TestAppContext,
 100) {
 101    let mut server = TestServer::start(executor.clone()).await;
 102
 103    let client_a = server.create_client(cx_a, "user_a").await;
 104    let client_b = server.create_client(cx_b, "user_b").await;
 105    let client_c = server.create_client(cx_c, "user_c").await;
 106    server
 107        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 108        .await;
 109
 110    let active_call_a = cx_a.read(ActiveCall::global);
 111    let active_call_b = cx_b.read(ActiveCall::global);
 112    let active_call_c = cx_c.read(ActiveCall::global);
 113
 114    // Call user B from client A.
 115    active_call_a
 116        .update(cx_a, |call, cx| {
 117            call.invite(client_b.user_id().unwrap(), None, cx)
 118        })
 119        .await
 120        .unwrap();
 121    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 122    executor.run_until_parked();
 123    assert_eq!(
 124        room_participants(&room_a, cx_a),
 125        RoomParticipants {
 126            remote: Default::default(),
 127            pending: vec!["user_b".to_string()]
 128        }
 129    );
 130
 131    // User B receives the call.
 132
 133    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 134    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 135    assert_eq!(call_b.calling_user.github_login, "user_a");
 136
 137    // User B connects via another client and also receives a ring on the newly-connected client.
 138    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 139    let active_call_b2 = cx_b2.read(ActiveCall::global);
 140
 141    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
 142    executor.run_until_parked();
 143    let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
 144    assert_eq!(call_b2.calling_user.github_login, "user_a");
 145
 146    // User B joins the room using the first client.
 147    active_call_b
 148        .update(cx_b, |call, cx| call.accept_incoming(cx))
 149        .await
 150        .unwrap();
 151
 152    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 153    assert!(incoming_call_b.next().await.unwrap().is_none());
 154
 155    executor.run_until_parked();
 156    assert_eq!(
 157        room_participants(&room_a, cx_a),
 158        RoomParticipants {
 159            remote: vec!["user_b".to_string()],
 160            pending: Default::default()
 161        }
 162    );
 163    assert_eq!(
 164        room_participants(&room_b, cx_b),
 165        RoomParticipants {
 166            remote: vec!["user_a".to_string()],
 167            pending: Default::default()
 168        }
 169    );
 170
 171    // Call user C from client B.
 172
 173    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 174    active_call_b
 175        .update(cx_b, |call, cx| {
 176            call.invite(client_c.user_id().unwrap(), None, cx)
 177        })
 178        .await
 179        .unwrap();
 180
 181    executor.run_until_parked();
 182    assert_eq!(
 183        room_participants(&room_a, cx_a),
 184        RoomParticipants {
 185            remote: vec!["user_b".to_string()],
 186            pending: vec!["user_c".to_string()]
 187        }
 188    );
 189    assert_eq!(
 190        room_participants(&room_b, cx_b),
 191        RoomParticipants {
 192            remote: vec!["user_a".to_string()],
 193            pending: vec!["user_c".to_string()]
 194        }
 195    );
 196
 197    // User C receives the call, but declines it.
 198    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 199    assert_eq!(call_c.calling_user.github_login, "user_b");
 200    active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
 201    assert!(incoming_call_c.next().await.unwrap().is_none());
 202
 203    executor.run_until_parked();
 204    assert_eq!(
 205        room_participants(&room_a, cx_a),
 206        RoomParticipants {
 207            remote: vec!["user_b".to_string()],
 208            pending: Default::default()
 209        }
 210    );
 211    assert_eq!(
 212        room_participants(&room_b, cx_b),
 213        RoomParticipants {
 214            remote: vec!["user_a".to_string()],
 215            pending: Default::default()
 216        }
 217    );
 218
 219    // Call user C again from user A.
 220    active_call_a
 221        .update(cx_a, |call, cx| {
 222            call.invite(client_c.user_id().unwrap(), None, cx)
 223        })
 224        .await
 225        .unwrap();
 226
 227    executor.run_until_parked();
 228    assert_eq!(
 229        room_participants(&room_a, cx_a),
 230        RoomParticipants {
 231            remote: vec!["user_b".to_string()],
 232            pending: vec!["user_c".to_string()]
 233        }
 234    );
 235    assert_eq!(
 236        room_participants(&room_b, cx_b),
 237        RoomParticipants {
 238            remote: vec!["user_a".to_string()],
 239            pending: vec!["user_c".to_string()]
 240        }
 241    );
 242
 243    // User C accepts the call.
 244    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 245    assert_eq!(call_c.calling_user.github_login, "user_a");
 246    active_call_c
 247        .update(cx_c, |call, cx| call.accept_incoming(cx))
 248        .await
 249        .unwrap();
 250    assert!(incoming_call_c.next().await.unwrap().is_none());
 251
 252    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 253
 254    executor.run_until_parked();
 255    assert_eq!(
 256        room_participants(&room_a, cx_a),
 257        RoomParticipants {
 258            remote: vec!["user_b".to_string(), "user_c".to_string()],
 259            pending: Default::default()
 260        }
 261    );
 262    assert_eq!(
 263        room_participants(&room_b, cx_b),
 264        RoomParticipants {
 265            remote: vec!["user_a".to_string(), "user_c".to_string()],
 266            pending: Default::default()
 267        }
 268    );
 269    assert_eq!(
 270        room_participants(&room_c, cx_c),
 271        RoomParticipants {
 272            remote: vec!["user_a".to_string(), "user_b".to_string()],
 273            pending: Default::default()
 274        }
 275    );
 276
 277    // User A shares their screen
 278    let display = gpui::TestScreenCaptureSource::new();
 279    let events_b = active_call_events(cx_b);
 280    let events_c = active_call_events(cx_c);
 281    cx_a.set_screen_capture_sources(vec![display]);
 282    active_call_a
 283        .update(cx_a, |call, cx| {
 284            call.room()
 285                .unwrap()
 286                .update(cx, |room, cx| room.share_screen(cx))
 287        })
 288        .await
 289        .unwrap();
 290
 291    executor.run_until_parked();
 292
 293    // User B observes the remote screen sharing track.
 294    assert_eq!(events_b.borrow().len(), 1);
 295    let event_b = events_b.borrow().first().unwrap().clone();
 296    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
 297        assert_eq!(participant_id, client_a.peer_id().unwrap());
 298
 299        room_b.read_with(cx_b, |room, _| {
 300            assert_eq!(
 301                room.remote_participants()[&client_a.user_id().unwrap()]
 302                    .video_tracks
 303                    .len(),
 304                1
 305            );
 306        });
 307    } else {
 308        panic!("unexpected event")
 309    }
 310
 311    // User C observes the remote screen sharing track.
 312    assert_eq!(events_c.borrow().len(), 1);
 313    let event_c = events_c.borrow().first().unwrap().clone();
 314    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
 315        assert_eq!(participant_id, client_a.peer_id().unwrap());
 316
 317        room_c.read_with(cx_c, |room, _| {
 318            assert_eq!(
 319                room.remote_participants()[&client_a.user_id().unwrap()]
 320                    .video_tracks
 321                    .len(),
 322                1
 323            );
 324        });
 325    } else {
 326        panic!("unexpected event")
 327    }
 328
 329    // User A leaves the room.
 330    active_call_a
 331        .update(cx_a, |call, cx| {
 332            let hang_up = call.hang_up(cx);
 333            assert!(call.room().is_none());
 334            hang_up
 335        })
 336        .await
 337        .unwrap();
 338    executor.run_until_parked();
 339    assert_eq!(
 340        room_participants(&room_a, cx_a),
 341        RoomParticipants {
 342            remote: Default::default(),
 343            pending: Default::default()
 344        }
 345    );
 346    assert_eq!(
 347        room_participants(&room_b, cx_b),
 348        RoomParticipants {
 349            remote: vec!["user_c".to_string()],
 350            pending: Default::default()
 351        }
 352    );
 353    assert_eq!(
 354        room_participants(&room_c, cx_c),
 355        RoomParticipants {
 356            remote: vec!["user_b".to_string()],
 357            pending: Default::default()
 358        }
 359    );
 360
 361    // User B gets disconnected from the LiveKit server, which causes them
 362    // to automatically leave the room. User C leaves the room as well because
 363    // nobody else is in there.
 364    server
 365        .test_livekit_server
 366        .disconnect_client(client_b.user_id().unwrap().to_string())
 367        .await;
 368    executor.run_until_parked();
 369
 370    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 371
 372    active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
 373    assert_eq!(
 374        room_participants(&room_a, cx_a),
 375        RoomParticipants {
 376            remote: Default::default(),
 377            pending: Default::default()
 378        }
 379    );
 380    assert_eq!(
 381        room_participants(&room_b, cx_b),
 382        RoomParticipants {
 383            remote: Default::default(),
 384            pending: Default::default()
 385        }
 386    );
 387    assert_eq!(
 388        room_participants(&room_c, cx_c),
 389        RoomParticipants {
 390            remote: Default::default(),
 391            pending: Default::default()
 392        }
 393    );
 394}
 395
 396#[gpui::test(iterations = 10)]
 397async fn test_calling_multiple_users_simultaneously(
 398    executor: BackgroundExecutor,
 399    cx_a: &mut TestAppContext,
 400    cx_b: &mut TestAppContext,
 401    cx_c: &mut TestAppContext,
 402    cx_d: &mut TestAppContext,
 403) {
 404    let mut server = TestServer::start(executor.clone()).await;
 405
 406    let client_a = server.create_client(cx_a, "user_a").await;
 407    let client_b = server.create_client(cx_b, "user_b").await;
 408    let client_c = server.create_client(cx_c, "user_c").await;
 409    let client_d = server.create_client(cx_d, "user_d").await;
 410    server
 411        .make_contacts(&mut [
 412            (&client_a, cx_a),
 413            (&client_b, cx_b),
 414            (&client_c, cx_c),
 415            (&client_d, cx_d),
 416        ])
 417        .await;
 418
 419    let active_call_a = cx_a.read(ActiveCall::global);
 420    let active_call_b = cx_b.read(ActiveCall::global);
 421    let active_call_c = cx_c.read(ActiveCall::global);
 422    let active_call_d = cx_d.read(ActiveCall::global);
 423
 424    // Simultaneously call user B and user C from client A.
 425    let b_invite = active_call_a.update(cx_a, |call, cx| {
 426        call.invite(client_b.user_id().unwrap(), None, cx)
 427    });
 428    let c_invite = active_call_a.update(cx_a, |call, cx| {
 429        call.invite(client_c.user_id().unwrap(), None, cx)
 430    });
 431    b_invite.await.unwrap();
 432    c_invite.await.unwrap();
 433
 434    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 435    executor.run_until_parked();
 436    assert_eq!(
 437        room_participants(&room_a, cx_a),
 438        RoomParticipants {
 439            remote: Default::default(),
 440            pending: vec!["user_b".to_string(), "user_c".to_string()]
 441        }
 442    );
 443
 444    // Call client D from client A.
 445    active_call_a
 446        .update(cx_a, |call, cx| {
 447            call.invite(client_d.user_id().unwrap(), None, cx)
 448        })
 449        .await
 450        .unwrap();
 451    executor.run_until_parked();
 452    assert_eq!(
 453        room_participants(&room_a, cx_a),
 454        RoomParticipants {
 455            remote: Default::default(),
 456            pending: vec![
 457                "user_b".to_string(),
 458                "user_c".to_string(),
 459                "user_d".to_string()
 460            ]
 461        }
 462    );
 463
 464    // Accept the call on all clients simultaneously.
 465    let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx));
 466    let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx));
 467    let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx));
 468    accept_b.await.unwrap();
 469    accept_c.await.unwrap();
 470    accept_d.await.unwrap();
 471
 472    executor.run_until_parked();
 473
 474    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 475
 476    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 477
 478    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
 479    assert_eq!(
 480        room_participants(&room_a, cx_a),
 481        RoomParticipants {
 482            remote: vec![
 483                "user_b".to_string(),
 484                "user_c".to_string(),
 485                "user_d".to_string(),
 486            ],
 487            pending: Default::default()
 488        }
 489    );
 490    assert_eq!(
 491        room_participants(&room_b, cx_b),
 492        RoomParticipants {
 493            remote: vec![
 494                "user_a".to_string(),
 495                "user_c".to_string(),
 496                "user_d".to_string(),
 497            ],
 498            pending: Default::default()
 499        }
 500    );
 501    assert_eq!(
 502        room_participants(&room_c, cx_c),
 503        RoomParticipants {
 504            remote: vec![
 505                "user_a".to_string(),
 506                "user_b".to_string(),
 507                "user_d".to_string(),
 508            ],
 509            pending: Default::default()
 510        }
 511    );
 512    assert_eq!(
 513        room_participants(&room_d, cx_d),
 514        RoomParticipants {
 515            remote: vec![
 516                "user_a".to_string(),
 517                "user_b".to_string(),
 518                "user_c".to_string(),
 519            ],
 520            pending: Default::default()
 521        }
 522    );
 523}
 524
 525#[gpui::test(iterations = 10)]
 526async fn test_joining_channels_and_calling_multiple_users_simultaneously(
 527    executor: BackgroundExecutor,
 528    cx_a: &mut TestAppContext,
 529    cx_b: &mut TestAppContext,
 530    cx_c: &mut TestAppContext,
 531) {
 532    let mut server = TestServer::start(executor.clone()).await;
 533
 534    let client_a = server.create_client(cx_a, "user_a").await;
 535    let client_b = server.create_client(cx_b, "user_b").await;
 536    let client_c = server.create_client(cx_c, "user_c").await;
 537    server
 538        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 539        .await;
 540
 541    let channel_1 = server
 542        .make_channel(
 543            "channel1",
 544            None,
 545            (&client_a, cx_a),
 546            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 547        )
 548        .await;
 549
 550    let channel_2 = server
 551        .make_channel(
 552            "channel2",
 553            None,
 554            (&client_a, cx_a),
 555            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 556        )
 557        .await;
 558
 559    let active_call_a = cx_a.read(ActiveCall::global);
 560
 561    // Simultaneously join channel 1 and then channel 2
 562    active_call_a
 563        .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
 564        .detach();
 565    let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
 566
 567    join_channel_2.await.unwrap();
 568
 569    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 570    executor.run_until_parked();
 571
 572    assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
 573
 574    // Leave the room
 575    active_call_a
 576        .update(cx_a, |call, cx| call.hang_up(cx))
 577        .await
 578        .unwrap();
 579
 580    // Initiating invites and then joining a channel should fail gracefully
 581    let b_invite = active_call_a.update(cx_a, |call, cx| {
 582        call.invite(client_b.user_id().unwrap(), None, cx)
 583    });
 584    let c_invite = active_call_a.update(cx_a, |call, cx| {
 585        call.invite(client_c.user_id().unwrap(), None, cx)
 586    });
 587
 588    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 589
 590    b_invite.await.unwrap();
 591    c_invite.await.unwrap();
 592    join_channel.await.unwrap();
 593
 594    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 595    executor.run_until_parked();
 596
 597    assert_eq!(
 598        room_participants(&room_a, cx_a),
 599        RoomParticipants {
 600            remote: Default::default(),
 601            pending: vec!["user_b".to_string(), "user_c".to_string()]
 602        }
 603    );
 604
 605    assert_eq!(channel_id(&room_a, cx_a), None);
 606
 607    // Leave the room
 608    active_call_a
 609        .update(cx_a, |call, cx| call.hang_up(cx))
 610        .await
 611        .unwrap();
 612
 613    // Simultaneously join channel 1 and call user B and user C from client A.
 614    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 615
 616    let b_invite = active_call_a.update(cx_a, |call, cx| {
 617        call.invite(client_b.user_id().unwrap(), None, cx)
 618    });
 619    let c_invite = active_call_a.update(cx_a, |call, cx| {
 620        call.invite(client_c.user_id().unwrap(), None, cx)
 621    });
 622
 623    join_channel.await.unwrap();
 624    b_invite.await.unwrap();
 625    c_invite.await.unwrap();
 626
 627    active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 628    executor.run_until_parked();
 629}
 630
 631#[gpui::test(iterations = 10)]
 632async fn test_room_uniqueness(
 633    executor: BackgroundExecutor,
 634    cx_a: &mut TestAppContext,
 635    cx_a2: &mut TestAppContext,
 636    cx_b: &mut TestAppContext,
 637    cx_b2: &mut TestAppContext,
 638    cx_c: &mut TestAppContext,
 639) {
 640    let mut server = TestServer::start(executor.clone()).await;
 641    let client_a = server.create_client(cx_a, "user_a").await;
 642    let _client_a2 = server.create_client(cx_a2, "user_a").await;
 643    let client_b = server.create_client(cx_b, "user_b").await;
 644    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 645    let client_c = server.create_client(cx_c, "user_c").await;
 646    server
 647        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 648        .await;
 649
 650    let active_call_a = cx_a.read(ActiveCall::global);
 651    let active_call_a2 = cx_a2.read(ActiveCall::global);
 652    let active_call_b = cx_b.read(ActiveCall::global);
 653    let active_call_b2 = cx_b2.read(ActiveCall::global);
 654    let active_call_c = cx_c.read(ActiveCall::global);
 655
 656    // Call user B from client A.
 657    active_call_a
 658        .update(cx_a, |call, cx| {
 659            call.invite(client_b.user_id().unwrap(), None, cx)
 660        })
 661        .await
 662        .unwrap();
 663
 664    // Ensure a new room can't be created given user A just created one.
 665    active_call_a2
 666        .update(cx_a2, |call, cx| {
 667            call.invite(client_c.user_id().unwrap(), None, cx)
 668        })
 669        .await
 670        .unwrap_err();
 671
 672    active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
 673
 674    // User B receives the call from user A.
 675
 676    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 677    let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
 678    assert_eq!(call_b1.calling_user.github_login, "user_a");
 679
 680    // Ensure calling users A and B from client C fails.
 681    active_call_c
 682        .update(cx_c, |call, cx| {
 683            call.invite(client_a.user_id().unwrap(), None, cx)
 684        })
 685        .await
 686        .unwrap_err();
 687    active_call_c
 688        .update(cx_c, |call, cx| {
 689            call.invite(client_b.user_id().unwrap(), None, cx)
 690        })
 691        .await
 692        .unwrap_err();
 693
 694    // Ensure User B can't create a room while they still have an incoming call.
 695    active_call_b2
 696        .update(cx_b2, |call, cx| {
 697            call.invite(client_c.user_id().unwrap(), None, cx)
 698        })
 699        .await
 700        .unwrap_err();
 701
 702    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 703
 704    // User B joins the room and calling them after they've joined still fails.
 705    active_call_b
 706        .update(cx_b, |call, cx| call.accept_incoming(cx))
 707        .await
 708        .unwrap();
 709    active_call_c
 710        .update(cx_c, |call, cx| {
 711            call.invite(client_b.user_id().unwrap(), None, cx)
 712        })
 713        .await
 714        .unwrap_err();
 715
 716    // Ensure User B can't create a room while they belong to another room.
 717    active_call_b2
 718        .update(cx_b2, |call, cx| {
 719            call.invite(client_c.user_id().unwrap(), None, cx)
 720        })
 721        .await
 722        .unwrap_err();
 723
 724    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 725
 726    // Client C can successfully call client B after client B leaves the room.
 727    active_call_b
 728        .update(cx_b, |call, cx| call.hang_up(cx))
 729        .await
 730        .unwrap();
 731    executor.run_until_parked();
 732    active_call_c
 733        .update(cx_c, |call, cx| {
 734            call.invite(client_b.user_id().unwrap(), None, cx)
 735        })
 736        .await
 737        .unwrap();
 738    executor.run_until_parked();
 739    let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
 740    assert_eq!(call_b2.calling_user.github_login, "user_c");
 741}
 742
 743#[gpui::test(iterations = 10)]
 744async fn test_client_disconnecting_from_room(
 745    executor: BackgroundExecutor,
 746    cx_a: &mut TestAppContext,
 747    cx_b: &mut TestAppContext,
 748) {
 749    let mut server = TestServer::start(executor.clone()).await;
 750    let client_a = server.create_client(cx_a, "user_a").await;
 751    let client_b = server.create_client(cx_b, "user_b").await;
 752    server
 753        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 754        .await;
 755
 756    let active_call_a = cx_a.read(ActiveCall::global);
 757    let active_call_b = cx_b.read(ActiveCall::global);
 758
 759    // Call user B from client A.
 760    active_call_a
 761        .update(cx_a, |call, cx| {
 762            call.invite(client_b.user_id().unwrap(), None, cx)
 763        })
 764        .await
 765        .unwrap();
 766
 767    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 768
 769    // User B receives the call and joins the room.
 770
 771    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 772    incoming_call_b.next().await.unwrap().unwrap();
 773    active_call_b
 774        .update(cx_b, |call, cx| call.accept_incoming(cx))
 775        .await
 776        .unwrap();
 777
 778    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 779    executor.run_until_parked();
 780    assert_eq!(
 781        room_participants(&room_a, cx_a),
 782        RoomParticipants {
 783            remote: vec!["user_b".to_string()],
 784            pending: Default::default()
 785        }
 786    );
 787    assert_eq!(
 788        room_participants(&room_b, cx_b),
 789        RoomParticipants {
 790            remote: vec!["user_a".to_string()],
 791            pending: Default::default()
 792        }
 793    );
 794
 795    // User A automatically reconnects to the room upon disconnection.
 796    server.disconnect_client(client_a.peer_id().unwrap());
 797    executor.advance_clock(RECEIVE_TIMEOUT);
 798    executor.run_until_parked();
 799    assert_eq!(
 800        room_participants(&room_a, cx_a),
 801        RoomParticipants {
 802            remote: vec!["user_b".to_string()],
 803            pending: Default::default()
 804        }
 805    );
 806    assert_eq!(
 807        room_participants(&room_b, cx_b),
 808        RoomParticipants {
 809            remote: vec!["user_a".to_string()],
 810            pending: Default::default()
 811        }
 812    );
 813
 814    // When user A disconnects, both client A and B clear their room on the active call.
 815    server.forbid_connections();
 816    server.disconnect_client(client_a.peer_id().unwrap());
 817    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 818
 819    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
 820
 821    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 822    assert_eq!(
 823        room_participants(&room_a, cx_a),
 824        RoomParticipants {
 825            remote: Default::default(),
 826            pending: Default::default()
 827        }
 828    );
 829    assert_eq!(
 830        room_participants(&room_b, cx_b),
 831        RoomParticipants {
 832            remote: Default::default(),
 833            pending: Default::default()
 834        }
 835    );
 836
 837    // Allow user A to reconnect to the server.
 838    server.allow_connections();
 839    executor.advance_clock(RECEIVE_TIMEOUT);
 840
 841    // Call user B again from client A.
 842    active_call_a
 843        .update(cx_a, |call, cx| {
 844            call.invite(client_b.user_id().unwrap(), None, cx)
 845        })
 846        .await
 847        .unwrap();
 848
 849    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 850
 851    // User B receives the call and joins the room.
 852
 853    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 854    incoming_call_b.next().await.unwrap().unwrap();
 855    active_call_b
 856        .update(cx_b, |call, cx| call.accept_incoming(cx))
 857        .await
 858        .unwrap();
 859
 860    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 861    executor.run_until_parked();
 862    assert_eq!(
 863        room_participants(&room_a, cx_a),
 864        RoomParticipants {
 865            remote: vec!["user_b".to_string()],
 866            pending: Default::default()
 867        }
 868    );
 869    assert_eq!(
 870        room_participants(&room_b, cx_b),
 871        RoomParticipants {
 872            remote: vec!["user_a".to_string()],
 873            pending: Default::default()
 874        }
 875    );
 876
 877    // User B gets disconnected from the LiveKit server, which causes it
 878    // to automatically leave the room.
 879    server
 880        .test_livekit_server
 881        .disconnect_client(client_b.user_id().unwrap().to_string())
 882        .await;
 883    executor.run_until_parked();
 884    active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
 885    active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
 886    assert_eq!(
 887        room_participants(&room_a, cx_a),
 888        RoomParticipants {
 889            remote: Default::default(),
 890            pending: Default::default()
 891        }
 892    );
 893    assert_eq!(
 894        room_participants(&room_b, cx_b),
 895        RoomParticipants {
 896            remote: Default::default(),
 897            pending: Default::default()
 898        }
 899    );
 900}
 901
 902#[gpui::test(iterations = 10)]
 903async fn test_server_restarts(
 904    executor: BackgroundExecutor,
 905    cx_a: &mut TestAppContext,
 906    cx_b: &mut TestAppContext,
 907    cx_c: &mut TestAppContext,
 908    cx_d: &mut TestAppContext,
 909) {
 910    let mut server = TestServer::start(executor.clone()).await;
 911    let client_a = server.create_client(cx_a, "user_a").await;
 912    client_a
 913        .fs()
 914        .insert_tree("/a", json!({ "a.txt": "a-contents" }))
 915        .await;
 916
 917    // Invite client B to collaborate on a project
 918    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 919
 920    let client_b = server.create_client(cx_b, "user_b").await;
 921    let client_c = server.create_client(cx_c, "user_c").await;
 922    let client_d = server.create_client(cx_d, "user_d").await;
 923    server
 924        .make_contacts(&mut [
 925            (&client_a, cx_a),
 926            (&client_b, cx_b),
 927            (&client_c, cx_c),
 928            (&client_d, cx_d),
 929        ])
 930        .await;
 931
 932    let active_call_a = cx_a.read(ActiveCall::global);
 933    let active_call_b = cx_b.read(ActiveCall::global);
 934    let active_call_c = cx_c.read(ActiveCall::global);
 935    let active_call_d = cx_d.read(ActiveCall::global);
 936
 937    // User A calls users B, C, and D.
 938    active_call_a
 939        .update(cx_a, |call, cx| {
 940            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 941        })
 942        .await
 943        .unwrap();
 944    active_call_a
 945        .update(cx_a, |call, cx| {
 946            call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
 947        })
 948        .await
 949        .unwrap();
 950    active_call_a
 951        .update(cx_a, |call, cx| {
 952            call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
 953        })
 954        .await
 955        .unwrap();
 956
 957    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 958
 959    // User B receives the call and joins the room.
 960
 961    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 962    assert!(incoming_call_b.next().await.unwrap().is_some());
 963    active_call_b
 964        .update(cx_b, |call, cx| call.accept_incoming(cx))
 965        .await
 966        .unwrap();
 967
 968    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 969
 970    // User C receives the call and joins the room.
 971
 972    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 973    assert!(incoming_call_c.next().await.unwrap().is_some());
 974    active_call_c
 975        .update(cx_c, |call, cx| call.accept_incoming(cx))
 976        .await
 977        .unwrap();
 978
 979    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 980
 981    // User D receives the call but doesn't join the room yet.
 982
 983    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
 984    assert!(incoming_call_d.next().await.unwrap().is_some());
 985
 986    executor.run_until_parked();
 987    assert_eq!(
 988        room_participants(&room_a, cx_a),
 989        RoomParticipants {
 990            remote: vec!["user_b".to_string(), "user_c".to_string()],
 991            pending: vec!["user_d".to_string()]
 992        }
 993    );
 994    assert_eq!(
 995        room_participants(&room_b, cx_b),
 996        RoomParticipants {
 997            remote: vec!["user_a".to_string(), "user_c".to_string()],
 998            pending: vec!["user_d".to_string()]
 999        }
1000    );
1001    assert_eq!(
1002        room_participants(&room_c, cx_c),
1003        RoomParticipants {
1004            remote: vec!["user_a".to_string(), "user_b".to_string()],
1005            pending: vec!["user_d".to_string()]
1006        }
1007    );
1008
1009    // The server is torn down.
1010    server.reset().await;
1011
1012    // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
1013    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1014    executor.advance_clock(RECONNECT_TIMEOUT);
1015    assert_eq!(
1016        room_participants(&room_a, cx_a),
1017        RoomParticipants {
1018            remote: vec!["user_b".to_string(), "user_c".to_string()],
1019            pending: vec!["user_d".to_string()]
1020        }
1021    );
1022    assert_eq!(
1023        room_participants(&room_b, cx_b),
1024        RoomParticipants {
1025            remote: vec!["user_a".to_string(), "user_c".to_string()],
1026            pending: vec!["user_d".to_string()]
1027        }
1028    );
1029    assert_eq!(
1030        room_participants(&room_c, cx_c),
1031        RoomParticipants {
1032            remote: vec![],
1033            pending: vec![]
1034        }
1035    );
1036
1037    // User D is notified again of the incoming call and accepts it.
1038    assert!(incoming_call_d.next().await.unwrap().is_some());
1039    active_call_d
1040        .update(cx_d, |call, cx| call.accept_incoming(cx))
1041        .await
1042        .unwrap();
1043    executor.run_until_parked();
1044
1045    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
1046    assert_eq!(
1047        room_participants(&room_a, cx_a),
1048        RoomParticipants {
1049            remote: vec![
1050                "user_b".to_string(),
1051                "user_c".to_string(),
1052                "user_d".to_string(),
1053            ],
1054            pending: vec![]
1055        }
1056    );
1057    assert_eq!(
1058        room_participants(&room_b, cx_b),
1059        RoomParticipants {
1060            remote: vec![
1061                "user_a".to_string(),
1062                "user_c".to_string(),
1063                "user_d".to_string(),
1064            ],
1065            pending: vec![]
1066        }
1067    );
1068    assert_eq!(
1069        room_participants(&room_c, cx_c),
1070        RoomParticipants {
1071            remote: vec![],
1072            pending: vec![]
1073        }
1074    );
1075    assert_eq!(
1076        room_participants(&room_d, cx_d),
1077        RoomParticipants {
1078            remote: vec![
1079                "user_a".to_string(),
1080                "user_b".to_string(),
1081                "user_c".to_string(),
1082            ],
1083            pending: vec![]
1084        }
1085    );
1086
1087    // The server finishes restarting, cleaning up stale connections.
1088    server.start().await.unwrap();
1089    executor.advance_clock(CLEANUP_TIMEOUT);
1090    assert_eq!(
1091        room_participants(&room_a, cx_a),
1092        RoomParticipants {
1093            remote: vec!["user_b".to_string(), "user_d".to_string()],
1094            pending: vec![]
1095        }
1096    );
1097    assert_eq!(
1098        room_participants(&room_b, cx_b),
1099        RoomParticipants {
1100            remote: vec!["user_a".to_string(), "user_d".to_string()],
1101            pending: vec![]
1102        }
1103    );
1104    assert_eq!(
1105        room_participants(&room_c, cx_c),
1106        RoomParticipants {
1107            remote: vec![],
1108            pending: vec![]
1109        }
1110    );
1111    assert_eq!(
1112        room_participants(&room_d, cx_d),
1113        RoomParticipants {
1114            remote: vec!["user_a".to_string(), "user_b".to_string()],
1115            pending: vec![]
1116        }
1117    );
1118
1119    // User D hangs up.
1120    active_call_d
1121        .update(cx_d, |call, cx| call.hang_up(cx))
1122        .await
1123        .unwrap();
1124    executor.run_until_parked();
1125    assert_eq!(
1126        room_participants(&room_a, cx_a),
1127        RoomParticipants {
1128            remote: vec!["user_b".to_string()],
1129            pending: vec![]
1130        }
1131    );
1132    assert_eq!(
1133        room_participants(&room_b, cx_b),
1134        RoomParticipants {
1135            remote: vec!["user_a".to_string()],
1136            pending: vec![]
1137        }
1138    );
1139    assert_eq!(
1140        room_participants(&room_c, cx_c),
1141        RoomParticipants {
1142            remote: vec![],
1143            pending: vec![]
1144        }
1145    );
1146    assert_eq!(
1147        room_participants(&room_d, cx_d),
1148        RoomParticipants {
1149            remote: vec![],
1150            pending: vec![]
1151        }
1152    );
1153
1154    // User B calls user D again.
1155    active_call_b
1156        .update(cx_b, |call, cx| {
1157            call.invite(client_d.user_id().unwrap(), None, cx)
1158        })
1159        .await
1160        .unwrap();
1161
1162    // User D receives the call but doesn't join the room yet.
1163
1164    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
1165    assert!(incoming_call_d.next().await.unwrap().is_some());
1166    executor.run_until_parked();
1167    assert_eq!(
1168        room_participants(&room_a, cx_a),
1169        RoomParticipants {
1170            remote: vec!["user_b".to_string()],
1171            pending: vec!["user_d".to_string()]
1172        }
1173    );
1174    assert_eq!(
1175        room_participants(&room_b, cx_b),
1176        RoomParticipants {
1177            remote: vec!["user_a".to_string()],
1178            pending: vec!["user_d".to_string()]
1179        }
1180    );
1181
1182    // The server is torn down.
1183    server.reset().await;
1184
1185    // Users A and B have troubles reconnecting, so they leave the room.
1186    client_a.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1187    client_b.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1188    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1189    executor.advance_clock(RECONNECT_TIMEOUT);
1190    assert_eq!(
1191        room_participants(&room_a, cx_a),
1192        RoomParticipants {
1193            remote: vec![],
1194            pending: vec![]
1195        }
1196    );
1197    assert_eq!(
1198        room_participants(&room_b, cx_b),
1199        RoomParticipants {
1200            remote: vec![],
1201            pending: vec![]
1202        }
1203    );
1204
1205    // User D is notified again of the incoming call but doesn't accept it.
1206    assert!(incoming_call_d.next().await.unwrap().is_some());
1207
1208    // The server finishes restarting, cleaning up stale connections and canceling the
1209    // call to user D because the room has become empty.
1210    server.start().await.unwrap();
1211    executor.advance_clock(CLEANUP_TIMEOUT);
1212    assert!(incoming_call_d.next().await.unwrap().is_none());
1213}
1214
1215#[gpui::test(iterations = 10)]
1216async fn test_calls_on_multiple_connections(
1217    executor: BackgroundExecutor,
1218    cx_a: &mut TestAppContext,
1219    cx_b1: &mut TestAppContext,
1220    cx_b2: &mut TestAppContext,
1221) {
1222    let mut server = TestServer::start(executor.clone()).await;
1223    let client_a = server.create_client(cx_a, "user_a").await;
1224    let client_b1 = server.create_client(cx_b1, "user_b").await;
1225    let client_b2 = server.create_client(cx_b2, "user_b").await;
1226    server
1227        .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
1228        .await;
1229
1230    let active_call_a = cx_a.read(ActiveCall::global);
1231    let active_call_b1 = cx_b1.read(ActiveCall::global);
1232    let active_call_b2 = cx_b2.read(ActiveCall::global);
1233
1234    let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
1235
1236    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
1237    assert!(incoming_call_b1.next().await.unwrap().is_none());
1238    assert!(incoming_call_b2.next().await.unwrap().is_none());
1239
1240    // Call user B from client A, ensuring both clients for user B ring.
1241    active_call_a
1242        .update(cx_a, |call, cx| {
1243            call.invite(client_b1.user_id().unwrap(), None, cx)
1244        })
1245        .await
1246        .unwrap();
1247    executor.run_until_parked();
1248    assert!(incoming_call_b1.next().await.unwrap().is_some());
1249    assert!(incoming_call_b2.next().await.unwrap().is_some());
1250
1251    // User B declines the call on one of the two connections, causing both connections
1252    // to stop ringing.
1253    active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
1254    executor.run_until_parked();
1255    assert!(incoming_call_b1.next().await.unwrap().is_none());
1256    assert!(incoming_call_b2.next().await.unwrap().is_none());
1257
1258    // Call user B again from client A.
1259    active_call_a
1260        .update(cx_a, |call, cx| {
1261            call.invite(client_b1.user_id().unwrap(), None, cx)
1262        })
1263        .await
1264        .unwrap();
1265    executor.run_until_parked();
1266    assert!(incoming_call_b1.next().await.unwrap().is_some());
1267    assert!(incoming_call_b2.next().await.unwrap().is_some());
1268
1269    // User B accepts the call on one of the two connections, causing both connections
1270    // to stop ringing.
1271    active_call_b2
1272        .update(cx_b2, |call, cx| call.accept_incoming(cx))
1273        .await
1274        .unwrap();
1275    executor.run_until_parked();
1276    assert!(incoming_call_b1.next().await.unwrap().is_none());
1277    assert!(incoming_call_b2.next().await.unwrap().is_none());
1278
1279    // User B disconnects the client that is not on the call. Everything should be fine.
1280    client_b1.disconnect(&cx_b1.to_async());
1281    executor.advance_clock(RECEIVE_TIMEOUT);
1282    client_b1
1283        .authenticate_and_connect(false, &cx_b1.to_async())
1284        .await
1285        .into_response()
1286        .unwrap();
1287
1288    // User B hangs up, and user A calls them again.
1289    active_call_b2
1290        .update(cx_b2, |call, cx| call.hang_up(cx))
1291        .await
1292        .unwrap();
1293    executor.run_until_parked();
1294    active_call_a
1295        .update(cx_a, |call, cx| {
1296            call.invite(client_b1.user_id().unwrap(), None, cx)
1297        })
1298        .await
1299        .unwrap();
1300    executor.run_until_parked();
1301    assert!(incoming_call_b1.next().await.unwrap().is_some());
1302    assert!(incoming_call_b2.next().await.unwrap().is_some());
1303
1304    // User A cancels the call, causing both connections to stop ringing.
1305    active_call_a
1306        .update(cx_a, |call, cx| {
1307            call.cancel_invite(client_b1.user_id().unwrap(), cx)
1308        })
1309        .await
1310        .unwrap();
1311    executor.run_until_parked();
1312    assert!(incoming_call_b1.next().await.unwrap().is_none());
1313    assert!(incoming_call_b2.next().await.unwrap().is_none());
1314
1315    // User A calls user B again.
1316    active_call_a
1317        .update(cx_a, |call, cx| {
1318            call.invite(client_b1.user_id().unwrap(), None, cx)
1319        })
1320        .await
1321        .unwrap();
1322    executor.run_until_parked();
1323    assert!(incoming_call_b1.next().await.unwrap().is_some());
1324    assert!(incoming_call_b2.next().await.unwrap().is_some());
1325
1326    // User A hangs up, causing both connections to stop ringing.
1327    active_call_a
1328        .update(cx_a, |call, cx| call.hang_up(cx))
1329        .await
1330        .unwrap();
1331    executor.run_until_parked();
1332    assert!(incoming_call_b1.next().await.unwrap().is_none());
1333    assert!(incoming_call_b2.next().await.unwrap().is_none());
1334
1335    // User A calls user B again.
1336    active_call_a
1337        .update(cx_a, |call, cx| {
1338            call.invite(client_b1.user_id().unwrap(), None, cx)
1339        })
1340        .await
1341        .unwrap();
1342    executor.run_until_parked();
1343    assert!(incoming_call_b1.next().await.unwrap().is_some());
1344    assert!(incoming_call_b2.next().await.unwrap().is_some());
1345
1346    // User A disconnects, causing both connections to stop ringing.
1347    server.forbid_connections();
1348    server.disconnect_client(client_a.peer_id().unwrap());
1349    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1350    assert!(incoming_call_b1.next().await.unwrap().is_none());
1351    assert!(incoming_call_b2.next().await.unwrap().is_none());
1352
1353    // User A reconnects automatically, then calls user B again.
1354    server.allow_connections();
1355    executor.advance_clock(RECEIVE_TIMEOUT);
1356    active_call_a
1357        .update(cx_a, |call, cx| {
1358            call.invite(client_b1.user_id().unwrap(), None, cx)
1359        })
1360        .await
1361        .unwrap();
1362    executor.run_until_parked();
1363    assert!(incoming_call_b1.next().await.unwrap().is_some());
1364    assert!(incoming_call_b2.next().await.unwrap().is_some());
1365
1366    // User B disconnects all clients, causing user A to no longer see a pending call for them.
1367    server.forbid_connections();
1368    server.disconnect_client(client_b1.peer_id().unwrap());
1369    server.disconnect_client(client_b2.peer_id().unwrap());
1370    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1371
1372    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
1373}
1374
1375#[gpui::test(iterations = 10)]
1376async fn test_unshare_project(
1377    executor: BackgroundExecutor,
1378    cx_a: &mut TestAppContext,
1379    cx_b: &mut TestAppContext,
1380    cx_c: &mut TestAppContext,
1381) {
1382    let mut server = TestServer::start(executor.clone()).await;
1383    let client_a = server.create_client(cx_a, "user_a").await;
1384    let client_b = server.create_client(cx_b, "user_b").await;
1385    let client_c = server.create_client(cx_c, "user_c").await;
1386    server
1387        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1388        .await;
1389
1390    let active_call_a = cx_a.read(ActiveCall::global);
1391    let active_call_b = cx_b.read(ActiveCall::global);
1392
1393    client_a
1394        .fs()
1395        .insert_tree(
1396            "/a",
1397            json!({
1398                "a.txt": "a-contents",
1399                "b.txt": "b-contents",
1400            }),
1401        )
1402        .await;
1403
1404    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1405    let project_id = active_call_a
1406        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1407        .await
1408        .unwrap();
1409
1410    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1411    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1412    executor.run_until_parked();
1413
1414    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1415
1416    project_b
1417        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1418        .await
1419        .unwrap();
1420
1421    // When client B leaves the room, the project becomes read-only.
1422    active_call_b
1423        .update(cx_b, |call, cx| call.hang_up(cx))
1424        .await
1425        .unwrap();
1426    executor.run_until_parked();
1427
1428    assert!(project_b.read_with(cx_b, |project, cx| project.is_disconnected(cx)));
1429
1430    // Client C opens the project.
1431    let project_c = client_c.join_remote_project(project_id, cx_c).await;
1432
1433    // When client A unshares the project, client C's project becomes read-only.
1434    project_a
1435        .update(cx_a, |project, cx| project.unshare(cx))
1436        .unwrap();
1437    executor.run_until_parked();
1438
1439    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
1440
1441    assert!(project_c.read_with(cx_c, |project, cx| project.is_disconnected(cx)));
1442
1443    // Client C can open the project again after client A re-shares.
1444    let project_id = active_call_a
1445        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1446        .await
1447        .unwrap();
1448    let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
1449    executor.run_until_parked();
1450
1451    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1452    project_c2
1453        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1454        .await
1455        .unwrap();
1456
1457    // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1458    active_call_a
1459        .update(cx_a, |call, cx| call.hang_up(cx))
1460        .await
1461        .unwrap();
1462    executor.run_until_parked();
1463
1464    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1465
1466    project_c2.read_with(cx_c, |project, cx| {
1467        assert!(project.is_disconnected(cx));
1468        assert!(project.collaborators().is_empty());
1469    });
1470}
1471
1472#[gpui::test(iterations = 10)]
1473async fn test_project_reconnect(
1474    executor: BackgroundExecutor,
1475    cx_a: &mut TestAppContext,
1476    cx_b: &mut TestAppContext,
1477) {
1478    let mut server = TestServer::start(executor.clone()).await;
1479    let client_a = server.create_client(cx_a, "user_a").await;
1480    let client_b = server.create_client(cx_b, "user_b").await;
1481    server
1482        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1483        .await;
1484
1485    cx_b.update(editor::init);
1486
1487    client_a
1488        .fs()
1489        .insert_tree(
1490            path!("/root-1"),
1491            json!({
1492                "dir1": {
1493                    "a.txt": "a",
1494                    "b.txt": "b",
1495                    "subdir1": {
1496                        "c.txt": "c",
1497                        "d.txt": "d",
1498                        "e.txt": "e",
1499                    }
1500                },
1501                "dir2": {
1502                    "v.txt": "v",
1503                },
1504                "dir3": {
1505                    "w.txt": "w",
1506                    "x.txt": "x",
1507                    "y.txt": "y",
1508                },
1509                "dir4": {
1510                    "z.txt": "z",
1511                },
1512            }),
1513        )
1514        .await;
1515    client_a
1516        .fs()
1517        .insert_tree(
1518            path!("/root-2"),
1519            json!({
1520                "2.txt": "2",
1521            }),
1522        )
1523        .await;
1524    client_a
1525        .fs()
1526        .insert_tree(
1527            path!("/root-3"),
1528            json!({
1529                "3.txt": "3",
1530            }),
1531        )
1532        .await;
1533
1534    let active_call_a = cx_a.read(ActiveCall::global);
1535    let (project_a1, _) = client_a
1536        .build_local_project(path!("/root-1/dir1"), cx_a)
1537        .await;
1538    let (project_a2, _) = client_a.build_local_project(path!("/root-2"), cx_a).await;
1539    let (project_a3, _) = client_a.build_local_project(path!("/root-3"), cx_a).await;
1540    let worktree_a1 =
1541        project_a1.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1542    let project1_id = active_call_a
1543        .update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
1544        .await
1545        .unwrap();
1546    let project2_id = active_call_a
1547        .update(cx_a, |call, cx| call.share_project(project_a2.clone(), cx))
1548        .await
1549        .unwrap();
1550    let project3_id = active_call_a
1551        .update(cx_a, |call, cx| call.share_project(project_a3.clone(), cx))
1552        .await
1553        .unwrap();
1554
1555    let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
1556    let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
1557    let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
1558    executor.run_until_parked();
1559
1560    let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
1561        assert!(worktree.has_update_observer());
1562        worktree.id()
1563    });
1564    let (worktree_a2, _) = project_a1
1565        .update(cx_a, |p, cx| {
1566            p.find_or_create_worktree(path!("/root-1/dir2"), true, cx)
1567        })
1568        .await
1569        .unwrap();
1570    executor.run_until_parked();
1571
1572    let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
1573        assert!(tree.has_update_observer());
1574        tree.id()
1575    });
1576    executor.run_until_parked();
1577
1578    project_b1.read_with(cx_b, |project, cx| {
1579        assert!(project.worktree_for_id(worktree2_id, cx).is_some())
1580    });
1581
1582    let buffer_a1 = project_a1
1583        .update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1584        .await
1585        .unwrap();
1586    let buffer_b1 = project_b1
1587        .update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1588        .await
1589        .unwrap();
1590
1591    // Drop client A's connection.
1592    server.forbid_connections();
1593    server.disconnect_client(client_a.peer_id().unwrap());
1594    executor.advance_clock(RECEIVE_TIMEOUT);
1595
1596    project_a1.read_with(cx_a, |project, _| {
1597        assert!(project.is_shared());
1598        assert_eq!(project.collaborators().len(), 1);
1599    });
1600
1601    project_b1.read_with(cx_b, |project, cx| {
1602        assert!(!project.is_disconnected(cx));
1603        assert_eq!(project.collaborators().len(), 1);
1604    });
1605
1606    worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
1607
1608    // While client A is disconnected, add and remove files from client A's project.
1609    client_a
1610        .fs()
1611        .insert_tree(
1612            path!("/root-1/dir1/subdir2"),
1613            json!({
1614                "f.txt": "f-contents",
1615                "g.txt": "g-contents",
1616                "h.txt": "h-contents",
1617                "i.txt": "i-contents",
1618            }),
1619        )
1620        .await;
1621    client_a
1622        .fs()
1623        .remove_dir(
1624            path!("/root-1/dir1/subdir1").as_ref(),
1625            RemoveOptions {
1626                recursive: true,
1627                ..Default::default()
1628            },
1629        )
1630        .await
1631        .unwrap();
1632
1633    // While client A is disconnected, add and remove worktrees from client A's project.
1634    project_a1.update(cx_a, |project, cx| {
1635        project.remove_worktree(worktree2_id, cx)
1636    });
1637    let (worktree_a3, _) = project_a1
1638        .update(cx_a, |p, cx| {
1639            p.find_or_create_worktree(path!("/root-1/dir3"), true, cx)
1640        })
1641        .await
1642        .unwrap();
1643    worktree_a3
1644        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
1645        .await;
1646
1647    let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
1648        assert!(!tree.has_update_observer());
1649        tree.id()
1650    });
1651    executor.run_until_parked();
1652
1653    // While client A is disconnected, close project 2
1654    cx_a.update(|_| drop(project_a2));
1655
1656    // While client A is disconnected, mutate a buffer on both the host and the guest.
1657    buffer_a1.update(cx_a, |buf, cx| buf.edit([(0..0, "W")], None, cx));
1658    buffer_b1.update(cx_b, |buf, cx| buf.edit([(1..1, "Z")], None, cx));
1659    executor.run_until_parked();
1660
1661    // Client A reconnects. Their project is re-shared, and client B re-joins it.
1662    server.allow_connections();
1663    client_a
1664        .authenticate_and_connect(false, &cx_a.to_async())
1665        .await
1666        .into_response()
1667        .unwrap();
1668    executor.run_until_parked();
1669
1670    project_a1.read_with(cx_a, |project, cx| {
1671        assert!(project.is_shared());
1672        assert!(worktree_a1.read(cx).has_update_observer());
1673        assert_eq!(
1674            worktree_a1
1675                .read(cx)
1676                .snapshot()
1677                .paths()
1678                .map(|p| p.to_str().unwrap())
1679                .collect::<Vec<_>>(),
1680            vec![
1681                separator!("a.txt"),
1682                separator!("b.txt"),
1683                separator!("subdir2"),
1684                separator!("subdir2/f.txt"),
1685                separator!("subdir2/g.txt"),
1686                separator!("subdir2/h.txt"),
1687                separator!("subdir2/i.txt")
1688            ]
1689        );
1690        assert!(worktree_a3.read(cx).has_update_observer());
1691        assert_eq!(
1692            worktree_a3
1693                .read(cx)
1694                .snapshot()
1695                .paths()
1696                .map(|p| p.to_str().unwrap())
1697                .collect::<Vec<_>>(),
1698            vec!["w.txt", "x.txt", "y.txt"]
1699        );
1700    });
1701
1702    project_b1.read_with(cx_b, |project, cx| {
1703        assert!(!project.is_disconnected(cx));
1704        assert_eq!(
1705            project
1706                .worktree_for_id(worktree1_id, cx)
1707                .unwrap()
1708                .read(cx)
1709                .snapshot()
1710                .paths()
1711                .map(|p| p.to_str().unwrap())
1712                .collect::<Vec<_>>(),
1713            vec![
1714                separator!("a.txt"),
1715                separator!("b.txt"),
1716                separator!("subdir2"),
1717                separator!("subdir2/f.txt"),
1718                separator!("subdir2/g.txt"),
1719                separator!("subdir2/h.txt"),
1720                separator!("subdir2/i.txt")
1721            ]
1722        );
1723        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1724        assert_eq!(
1725            project
1726                .worktree_for_id(worktree3_id, cx)
1727                .unwrap()
1728                .read(cx)
1729                .snapshot()
1730                .paths()
1731                .map(|p| p.to_str().unwrap())
1732                .collect::<Vec<_>>(),
1733            vec!["w.txt", "x.txt", "y.txt"]
1734        );
1735    });
1736
1737    project_b2.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1738
1739    project_b3.read_with(cx_b, |project, cx| assert!(!project.is_disconnected(cx)));
1740
1741    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1742
1743    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1744
1745    // Drop client B's connection.
1746    server.forbid_connections();
1747    server.disconnect_client(client_b.peer_id().unwrap());
1748    executor.advance_clock(RECEIVE_TIMEOUT);
1749
1750    // While client B is disconnected, add and remove files from client A's project
1751    client_a
1752        .fs()
1753        .insert_file(path!("/root-1/dir1/subdir2/j.txt"), "j-contents".into())
1754        .await;
1755    client_a
1756        .fs()
1757        .remove_file(
1758            path!("/root-1/dir1/subdir2/i.txt").as_ref(),
1759            Default::default(),
1760        )
1761        .await
1762        .unwrap();
1763
1764    // While client B is disconnected, add and remove worktrees from client A's project.
1765    let (worktree_a4, _) = project_a1
1766        .update(cx_a, |p, cx| {
1767            p.find_or_create_worktree(path!("/root-1/dir4"), true, cx)
1768        })
1769        .await
1770        .unwrap();
1771    executor.run_until_parked();
1772
1773    let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
1774        assert!(tree.has_update_observer());
1775        tree.id()
1776    });
1777    project_a1.update(cx_a, |project, cx| {
1778        project.remove_worktree(worktree3_id, cx)
1779    });
1780    executor.run_until_parked();
1781
1782    // While client B is disconnected, mutate a buffer on both the host and the guest.
1783    buffer_a1.update(cx_a, |buf, cx| buf.edit([(1..1, "X")], None, cx));
1784    buffer_b1.update(cx_b, |buf, cx| buf.edit([(2..2, "Y")], None, cx));
1785    executor.run_until_parked();
1786
1787    // While disconnected, close project 3
1788    cx_a.update(|_| drop(project_a3));
1789
1790    // Client B reconnects. They re-join the room and the remaining shared project.
1791    server.allow_connections();
1792    client_b
1793        .authenticate_and_connect(false, &cx_b.to_async())
1794        .await
1795        .into_response()
1796        .unwrap();
1797    executor.run_until_parked();
1798
1799    project_b1.read_with(cx_b, |project, cx| {
1800        assert!(!project.is_disconnected(cx));
1801        assert_eq!(
1802            project
1803                .worktree_for_id(worktree1_id, cx)
1804                .unwrap()
1805                .read(cx)
1806                .snapshot()
1807                .paths()
1808                .map(|p| p.to_str().unwrap())
1809                .collect::<Vec<_>>(),
1810            vec![
1811                separator!("a.txt"),
1812                separator!("b.txt"),
1813                separator!("subdir2"),
1814                separator!("subdir2/f.txt"),
1815                separator!("subdir2/g.txt"),
1816                separator!("subdir2/h.txt"),
1817                separator!("subdir2/j.txt")
1818            ]
1819        );
1820        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1821        assert_eq!(
1822            project
1823                .worktree_for_id(worktree4_id, cx)
1824                .unwrap()
1825                .read(cx)
1826                .snapshot()
1827                .paths()
1828                .map(|p| p.to_str().unwrap())
1829                .collect::<Vec<_>>(),
1830            vec!["z.txt"]
1831        );
1832    });
1833
1834    project_b3.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1835
1836    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1837
1838    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1839}
1840
1841#[gpui::test(iterations = 10)]
1842async fn test_active_call_events(
1843    executor: BackgroundExecutor,
1844    cx_a: &mut TestAppContext,
1845    cx_b: &mut TestAppContext,
1846) {
1847    let mut server = TestServer::start(executor.clone()).await;
1848    let client_a = server.create_client(cx_a, "user_a").await;
1849    let client_b = server.create_client(cx_b, "user_b").await;
1850    client_a.fs().insert_tree("/a", json!({})).await;
1851    client_b.fs().insert_tree("/b", json!({})).await;
1852
1853    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1854    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1855
1856    server
1857        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1858        .await;
1859    executor.run_until_parked();
1860
1861    let active_call_a = cx_a.read(ActiveCall::global);
1862    let active_call_b = cx_b.read(ActiveCall::global);
1863
1864    let events_a = active_call_events(cx_a);
1865    let events_b = active_call_events(cx_b);
1866
1867    let project_a_id = active_call_a
1868        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1869        .await
1870        .unwrap();
1871    executor.run_until_parked();
1872    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1873    assert_eq!(
1874        mem::take(&mut *events_b.borrow_mut()),
1875        vec![room::Event::RemoteProjectShared {
1876            owner: Arc::new(User {
1877                id: client_a.user_id().unwrap(),
1878                github_login: "user_a".to_string(),
1879                avatar_uri: "avatar_a".into(),
1880                name: None,
1881                email: None,
1882            }),
1883            project_id: project_a_id,
1884            worktree_root_names: vec!["a".to_string()],
1885        }]
1886    );
1887
1888    let project_b_id = active_call_b
1889        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1890        .await
1891        .unwrap();
1892    executor.run_until_parked();
1893    assert_eq!(
1894        mem::take(&mut *events_a.borrow_mut()),
1895        vec![room::Event::RemoteProjectShared {
1896            owner: Arc::new(User {
1897                id: client_b.user_id().unwrap(),
1898                github_login: "user_b".to_string(),
1899                avatar_uri: "avatar_b".into(),
1900                name: None,
1901                email: None,
1902            }),
1903            project_id: project_b_id,
1904            worktree_root_names: vec!["b".to_string()]
1905        }]
1906    );
1907    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1908
1909    // Sharing a project twice is idempotent.
1910    let project_b_id_2 = active_call_b
1911        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1912        .await
1913        .unwrap();
1914    assert_eq!(project_b_id_2, project_b_id);
1915    executor.run_until_parked();
1916    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1917    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1918
1919    // Unsharing a project should dispatch the RemoteProjectUnshared event.
1920    active_call_a
1921        .update(cx_a, |call, cx| call.hang_up(cx))
1922        .await
1923        .unwrap();
1924    executor.run_until_parked();
1925
1926    assert_eq!(
1927        mem::take(&mut *events_a.borrow_mut()),
1928        vec![room::Event::RoomLeft { channel_id: None }]
1929    );
1930    assert_eq!(
1931        mem::take(&mut *events_b.borrow_mut()),
1932        vec![room::Event::RemoteProjectUnshared {
1933            project_id: project_a_id,
1934        }]
1935    );
1936}
1937
1938fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1939    let events = Rc::new(RefCell::new(Vec::new()));
1940    let active_call = cx.read(ActiveCall::global);
1941    cx.update({
1942        let events = events.clone();
1943        |cx| {
1944            cx.subscribe(&active_call, move |_, event, _| {
1945                events.borrow_mut().push(event.clone())
1946            })
1947            .detach()
1948        }
1949    });
1950    events
1951}
1952
1953#[gpui::test]
1954async fn test_mute_deafen(
1955    executor: BackgroundExecutor,
1956    cx_a: &mut TestAppContext,
1957    cx_b: &mut TestAppContext,
1958    cx_c: &mut TestAppContext,
1959) {
1960    let mut server = TestServer::start(executor.clone()).await;
1961    let client_a = server.create_client(cx_a, "user_a").await;
1962    let client_b = server.create_client(cx_b, "user_b").await;
1963    let client_c = server.create_client(cx_c, "user_c").await;
1964
1965    server
1966        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1967        .await;
1968
1969    let active_call_a = cx_a.read(ActiveCall::global);
1970    let active_call_b = cx_b.read(ActiveCall::global);
1971    let active_call_c = cx_c.read(ActiveCall::global);
1972
1973    // User A calls user B, B answers.
1974    active_call_a
1975        .update(cx_a, |call, cx| {
1976            call.invite(client_b.user_id().unwrap(), None, cx)
1977        })
1978        .await
1979        .unwrap();
1980    executor.run_until_parked();
1981    active_call_b
1982        .update(cx_b, |call, cx| call.accept_incoming(cx))
1983        .await
1984        .unwrap();
1985    executor.run_until_parked();
1986
1987    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1988    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1989
1990    room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
1991    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
1992
1993    // Users A and B are both unmuted.
1994    assert_eq!(
1995        participant_audio_state(&room_a, cx_a),
1996        &[ParticipantAudioState {
1997            user_id: client_b.user_id().unwrap(),
1998            is_muted: false,
1999            audio_tracks_playing: vec![true],
2000        }]
2001    );
2002    assert_eq!(
2003        participant_audio_state(&room_b, cx_b),
2004        &[ParticipantAudioState {
2005            user_id: client_a.user_id().unwrap(),
2006            is_muted: false,
2007            audio_tracks_playing: vec![true],
2008        }]
2009    );
2010
2011    // User A mutes
2012    room_a.update(cx_a, |room, cx| room.toggle_mute(cx));
2013    executor.run_until_parked();
2014
2015    // User A hears user B, but B doesn't hear A.
2016    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2017    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2018    assert_eq!(
2019        participant_audio_state(&room_a, cx_a),
2020        &[ParticipantAudioState {
2021            user_id: client_b.user_id().unwrap(),
2022            is_muted: false,
2023            audio_tracks_playing: vec![true],
2024        }]
2025    );
2026    assert_eq!(
2027        participant_audio_state(&room_b, cx_b),
2028        &[ParticipantAudioState {
2029            user_id: client_a.user_id().unwrap(),
2030            is_muted: true,
2031            audio_tracks_playing: vec![true],
2032        }]
2033    );
2034
2035    // User A deafens
2036    room_a.update(cx_a, |room, cx| room.toggle_deafen(cx));
2037    executor.run_until_parked();
2038
2039    // User A does not hear user B.
2040    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2041    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2042    assert_eq!(
2043        participant_audio_state(&room_a, cx_a),
2044        &[ParticipantAudioState {
2045            user_id: client_b.user_id().unwrap(),
2046            is_muted: false,
2047            audio_tracks_playing: vec![false],
2048        }]
2049    );
2050    assert_eq!(
2051        participant_audio_state(&room_b, cx_b),
2052        &[ParticipantAudioState {
2053            user_id: client_a.user_id().unwrap(),
2054            is_muted: true,
2055            audio_tracks_playing: vec![true],
2056        }]
2057    );
2058
2059    // User B calls user C, C joins.
2060    active_call_b
2061        .update(cx_b, |call, cx| {
2062            call.invite(client_c.user_id().unwrap(), None, cx)
2063        })
2064        .await
2065        .unwrap();
2066    executor.run_until_parked();
2067    active_call_c
2068        .update(cx_c, |call, cx| call.accept_incoming(cx))
2069        .await
2070        .unwrap();
2071    executor.run_until_parked();
2072
2073    // User A does not hear users B or C.
2074    assert_eq!(
2075        participant_audio_state(&room_a, cx_a),
2076        &[
2077            ParticipantAudioState {
2078                user_id: client_b.user_id().unwrap(),
2079                is_muted: false,
2080                audio_tracks_playing: vec![false],
2081            },
2082            ParticipantAudioState {
2083                user_id: client_c.user_id().unwrap(),
2084                is_muted: false,
2085                audio_tracks_playing: vec![false],
2086            }
2087        ]
2088    );
2089    assert_eq!(
2090        participant_audio_state(&room_b, cx_b),
2091        &[
2092            ParticipantAudioState {
2093                user_id: client_a.user_id().unwrap(),
2094                is_muted: true,
2095                audio_tracks_playing: vec![true],
2096            },
2097            ParticipantAudioState {
2098                user_id: client_c.user_id().unwrap(),
2099                is_muted: false,
2100                audio_tracks_playing: vec![true],
2101            }
2102        ]
2103    );
2104
2105    #[derive(PartialEq, Eq, Debug)]
2106    struct ParticipantAudioState {
2107        user_id: u64,
2108        is_muted: bool,
2109        audio_tracks_playing: Vec<bool>,
2110    }
2111
2112    fn participant_audio_state(
2113        room: &Entity<Room>,
2114        cx: &TestAppContext,
2115    ) -> Vec<ParticipantAudioState> {
2116        room.read_with(cx, |room, _| {
2117            room.remote_participants()
2118                .iter()
2119                .map(|(user_id, participant)| ParticipantAudioState {
2120                    user_id: *user_id,
2121                    is_muted: participant.muted,
2122                    audio_tracks_playing: participant
2123                        .audio_tracks
2124                        .values()
2125                        .map(|(track, _)| track.enabled())
2126                        .collect(),
2127                })
2128                .collect::<Vec<_>>()
2129        })
2130    }
2131}
2132
2133#[gpui::test(iterations = 10)]
2134async fn test_room_location(
2135    executor: BackgroundExecutor,
2136    cx_a: &mut TestAppContext,
2137    cx_b: &mut TestAppContext,
2138) {
2139    let mut server = TestServer::start(executor.clone()).await;
2140    let client_a = server.create_client(cx_a, "user_a").await;
2141    let client_b = server.create_client(cx_b, "user_b").await;
2142    client_a.fs().insert_tree("/a", json!({})).await;
2143    client_b.fs().insert_tree("/b", json!({})).await;
2144
2145    let active_call_a = cx_a.read(ActiveCall::global);
2146    let active_call_b = cx_b.read(ActiveCall::global);
2147
2148    let a_notified = Rc::new(Cell::new(false));
2149    cx_a.update({
2150        let notified = a_notified.clone();
2151        |cx| {
2152            cx.observe(&active_call_a, move |_, _| notified.set(true))
2153                .detach()
2154        }
2155    });
2156
2157    let b_notified = Rc::new(Cell::new(false));
2158    cx_b.update({
2159        let b_notified = b_notified.clone();
2160        |cx| {
2161            cx.observe(&active_call_b, move |_, _| b_notified.set(true))
2162                .detach()
2163        }
2164    });
2165
2166    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2167    active_call_a
2168        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2169        .await
2170        .unwrap();
2171    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
2172
2173    server
2174        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2175        .await;
2176
2177    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
2178
2179    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
2180    executor.run_until_parked();
2181    assert!(a_notified.take());
2182    assert_eq!(
2183        participant_locations(&room_a, cx_a),
2184        vec![("user_b".to_string(), ParticipantLocation::External)]
2185    );
2186    assert!(b_notified.take());
2187    assert_eq!(
2188        participant_locations(&room_b, cx_b),
2189        vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
2190    );
2191
2192    let project_a_id = active_call_a
2193        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2194        .await
2195        .unwrap();
2196    executor.run_until_parked();
2197    assert!(a_notified.take());
2198    assert_eq!(
2199        participant_locations(&room_a, cx_a),
2200        vec![("user_b".to_string(), ParticipantLocation::External)]
2201    );
2202    assert!(b_notified.take());
2203    assert_eq!(
2204        participant_locations(&room_b, cx_b),
2205        vec![(
2206            "user_a".to_string(),
2207            ParticipantLocation::SharedProject {
2208                project_id: project_a_id
2209            }
2210        )]
2211    );
2212
2213    let project_b_id = active_call_b
2214        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
2215        .await
2216        .unwrap();
2217    executor.run_until_parked();
2218    assert!(a_notified.take());
2219    assert_eq!(
2220        participant_locations(&room_a, cx_a),
2221        vec![("user_b".to_string(), ParticipantLocation::External)]
2222    );
2223    assert!(b_notified.take());
2224    assert_eq!(
2225        participant_locations(&room_b, cx_b),
2226        vec![(
2227            "user_a".to_string(),
2228            ParticipantLocation::SharedProject {
2229                project_id: project_a_id
2230            }
2231        )]
2232    );
2233
2234    active_call_b
2235        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2236        .await
2237        .unwrap();
2238    executor.run_until_parked();
2239    assert!(a_notified.take());
2240    assert_eq!(
2241        participant_locations(&room_a, cx_a),
2242        vec![(
2243            "user_b".to_string(),
2244            ParticipantLocation::SharedProject {
2245                project_id: project_b_id
2246            }
2247        )]
2248    );
2249    assert!(b_notified.take());
2250    assert_eq!(
2251        participant_locations(&room_b, cx_b),
2252        vec![(
2253            "user_a".to_string(),
2254            ParticipantLocation::SharedProject {
2255                project_id: project_a_id
2256            }
2257        )]
2258    );
2259
2260    active_call_b
2261        .update(cx_b, |call, cx| call.set_location(None, cx))
2262        .await
2263        .unwrap();
2264    executor.run_until_parked();
2265    assert!(a_notified.take());
2266    assert_eq!(
2267        participant_locations(&room_a, cx_a),
2268        vec![("user_b".to_string(), ParticipantLocation::External)]
2269    );
2270    assert!(b_notified.take());
2271    assert_eq!(
2272        participant_locations(&room_b, cx_b),
2273        vec![(
2274            "user_a".to_string(),
2275            ParticipantLocation::SharedProject {
2276                project_id: project_a_id
2277            }
2278        )]
2279    );
2280
2281    fn participant_locations(
2282        room: &Entity<Room>,
2283        cx: &TestAppContext,
2284    ) -> Vec<(String, ParticipantLocation)> {
2285        room.read_with(cx, |room, _| {
2286            room.remote_participants()
2287                .values()
2288                .map(|participant| {
2289                    (
2290                        participant.user.github_login.to_string(),
2291                        participant.location,
2292                    )
2293                })
2294                .collect()
2295        })
2296    }
2297}
2298
2299#[gpui::test(iterations = 10)]
2300async fn test_propagate_saves_and_fs_changes(
2301    executor: BackgroundExecutor,
2302    cx_a: &mut TestAppContext,
2303    cx_b: &mut TestAppContext,
2304    cx_c: &mut TestAppContext,
2305) {
2306    let mut server = TestServer::start(executor.clone()).await;
2307    let client_a = server.create_client(cx_a, "user_a").await;
2308    let client_b = server.create_client(cx_b, "user_b").await;
2309    let client_c = server.create_client(cx_c, "user_c").await;
2310
2311    server
2312        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2313        .await;
2314    let active_call_a = cx_a.read(ActiveCall::global);
2315
2316    let rust = Arc::new(Language::new(
2317        LanguageConfig {
2318            name: "Rust".into(),
2319            matcher: LanguageMatcher {
2320                path_suffixes: vec!["rs".to_string()],
2321                ..Default::default()
2322            },
2323            ..Default::default()
2324        },
2325        Some(tree_sitter_rust::LANGUAGE.into()),
2326    ));
2327    let javascript = Arc::new(Language::new(
2328        LanguageConfig {
2329            name: "JavaScript".into(),
2330            matcher: LanguageMatcher {
2331                path_suffixes: vec!["js".to_string()],
2332                ..Default::default()
2333            },
2334            ..Default::default()
2335        },
2336        Some(tree_sitter_rust::LANGUAGE.into()),
2337    ));
2338    for client in [&client_a, &client_b, &client_c] {
2339        client.language_registry().add(rust.clone());
2340        client.language_registry().add(javascript.clone());
2341    }
2342
2343    client_a
2344        .fs()
2345        .insert_tree(
2346            path!("/a"),
2347            json!({
2348                "file1.rs": "",
2349                "file2": ""
2350            }),
2351        )
2352        .await;
2353    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2354
2355    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
2356    let project_id = active_call_a
2357        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2358        .await
2359        .unwrap();
2360
2361    // Join that worktree as clients B and C.
2362    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2363    let project_c = client_c.join_remote_project(project_id, cx_c).await;
2364
2365    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
2366
2367    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
2368
2369    // Open and edit a buffer as both guests B and C.
2370    let buffer_b = project_b
2371        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2372        .await
2373        .unwrap();
2374    let buffer_c = project_c
2375        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2376        .await
2377        .unwrap();
2378
2379    buffer_b.read_with(cx_b, |buffer, _| {
2380        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2381    });
2382
2383    buffer_c.read_with(cx_c, |buffer, _| {
2384        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2385    });
2386    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
2387    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
2388
2389    // Open and edit that buffer as the host.
2390    let buffer_a = project_a
2391        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2392        .await
2393        .unwrap();
2394
2395    executor.run_until_parked();
2396
2397    buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
2398    buffer_a.update(cx_a, |buf, cx| {
2399        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
2400    });
2401
2402    executor.run_until_parked();
2403
2404    buffer_a.read_with(cx_a, |buf, _| {
2405        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2406    });
2407
2408    buffer_b.read_with(cx_b, |buf, _| {
2409        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2410    });
2411
2412    buffer_c.read_with(cx_c, |buf, _| {
2413        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2414    });
2415
2416    // Edit the buffer as the host and concurrently save as guest B.
2417    let save_b = project_b.update(cx_b, |project, cx| {
2418        project.save_buffer(buffer_b.clone(), cx)
2419    });
2420    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
2421    save_b.await.unwrap();
2422    assert_eq!(
2423        client_a.fs().load("/a/file1.rs".as_ref()).await.unwrap(),
2424        "hi-a, i-am-c, i-am-b, i-am-a"
2425    );
2426
2427    executor.run_until_parked();
2428
2429    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
2430
2431    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
2432
2433    buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
2434
2435    // Make changes on host's file system, see those changes on guest worktrees.
2436    client_a
2437        .fs()
2438        .rename(
2439            path!("/a/file1.rs").as_ref(),
2440            path!("/a/file1.js").as_ref(),
2441            Default::default(),
2442        )
2443        .await
2444        .unwrap();
2445    client_a
2446        .fs()
2447        .rename(
2448            path!("/a/file2").as_ref(),
2449            path!("/a/file3").as_ref(),
2450            Default::default(),
2451        )
2452        .await
2453        .unwrap();
2454    client_a
2455        .fs()
2456        .insert_file(path!("/a/file4"), "4".into())
2457        .await;
2458    executor.run_until_parked();
2459
2460    worktree_a.read_with(cx_a, |tree, _| {
2461        assert_eq!(
2462            tree.paths()
2463                .map(|p| p.to_string_lossy())
2464                .collect::<Vec<_>>(),
2465            ["file1.js", "file3", "file4"]
2466        )
2467    });
2468
2469    worktree_b.read_with(cx_b, |tree, _| {
2470        assert_eq!(
2471            tree.paths()
2472                .map(|p| p.to_string_lossy())
2473                .collect::<Vec<_>>(),
2474            ["file1.js", "file3", "file4"]
2475        )
2476    });
2477
2478    worktree_c.read_with(cx_c, |tree, _| {
2479        assert_eq!(
2480            tree.paths()
2481                .map(|p| p.to_string_lossy())
2482                .collect::<Vec<_>>(),
2483            ["file1.js", "file3", "file4"]
2484        )
2485    });
2486
2487    // Ensure buffer files are updated as well.
2488
2489    buffer_a.read_with(cx_a, |buffer, _| {
2490        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2491        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2492    });
2493
2494    buffer_b.read_with(cx_b, |buffer, _| {
2495        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2496        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2497    });
2498
2499    buffer_c.read_with(cx_c, |buffer, _| {
2500        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2501        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2502    });
2503
2504    let new_buffer_a = project_a
2505        .update(cx_a, |p, cx| p.create_buffer(cx))
2506        .await
2507        .unwrap();
2508
2509    let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
2510    let new_buffer_b = project_b
2511        .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx))
2512        .await
2513        .unwrap();
2514
2515    new_buffer_b.read_with(cx_b, |buffer, _| {
2516        assert!(buffer.file().is_none());
2517    });
2518
2519    new_buffer_a.update(cx_a, |buffer, cx| {
2520        buffer.edit([(0..0, "ok")], None, cx);
2521    });
2522    project_a
2523        .update(cx_a, |project, cx| {
2524            let path = ProjectPath {
2525                path: Arc::from(Path::new("file3.rs")),
2526                worktree_id: worktree_a.read(cx).id(),
2527            };
2528
2529            project.save_buffer_as(new_buffer_a.clone(), path, cx)
2530        })
2531        .await
2532        .unwrap();
2533
2534    executor.run_until_parked();
2535
2536    new_buffer_b.read_with(cx_b, |buffer_b, _| {
2537        assert_eq!(
2538            buffer_b.file().unwrap().path().as_ref(),
2539            Path::new("file3.rs")
2540        );
2541
2542        new_buffer_a.read_with(cx_a, |buffer_a, _| {
2543            assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime());
2544            assert_eq!(buffer_b.saved_version(), buffer_a.saved_version());
2545        });
2546    });
2547}
2548
2549#[gpui::test(iterations = 10)]
2550async fn test_git_diff_base_change(
2551    executor: BackgroundExecutor,
2552    cx_a: &mut TestAppContext,
2553    cx_b: &mut TestAppContext,
2554) {
2555    let mut server = TestServer::start(executor.clone()).await;
2556    let client_a = server.create_client(cx_a, "user_a").await;
2557    let client_b = server.create_client(cx_b, "user_b").await;
2558    server
2559        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2560        .await;
2561    let active_call_a = cx_a.read(ActiveCall::global);
2562
2563    client_a
2564        .fs()
2565        .insert_tree(
2566            "/dir",
2567            json!({
2568            ".git": {},
2569            "sub": {
2570                ".git": {},
2571                "b.txt": "
2572                    one
2573                    two
2574                    three
2575                ".unindent(),
2576            },
2577            "a.txt": "
2578                    one
2579                    two
2580                    three
2581                ".unindent(),
2582            }),
2583        )
2584        .await;
2585
2586    let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2587    let project_id = active_call_a
2588        .update(cx_a, |call, cx| {
2589            call.share_project(project_local.clone(), cx)
2590        })
2591        .await
2592        .unwrap();
2593
2594    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2595
2596    let staged_text = "
2597        one
2598        three
2599    "
2600    .unindent();
2601
2602    let committed_text = "
2603        one
2604        TWO
2605        three
2606    "
2607    .unindent();
2608
2609    let new_committed_text = "
2610        one
2611        TWO_HUNDRED
2612        three
2613    "
2614    .unindent();
2615
2616    let new_staged_text = "
2617        one
2618        two
2619    "
2620    .unindent();
2621
2622    client_a.fs().set_index_for_repo(
2623        Path::new("/dir/.git"),
2624        &[("a.txt".into(), staged_text.clone())],
2625    );
2626    client_a.fs().set_head_for_repo(
2627        Path::new("/dir/.git"),
2628        &[("a.txt".into(), committed_text.clone())],
2629    );
2630
2631    // Create the buffer
2632    let buffer_local_a = project_local
2633        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2634        .await
2635        .unwrap();
2636    let local_unstaged_diff_a = project_local
2637        .update(cx_a, |p, cx| {
2638            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2639        })
2640        .await
2641        .unwrap();
2642
2643    // Wait for it to catch up to the new diff
2644    executor.run_until_parked();
2645    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2646        let buffer = buffer_local_a.read(cx);
2647        assert_eq!(
2648            diff.base_text_string().as_deref(),
2649            Some(staged_text.as_str())
2650        );
2651        assert_hunks(
2652            diff.hunks_in_row_range(0..4, buffer, cx),
2653            buffer,
2654            &diff.base_text_string().unwrap(),
2655            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2656        );
2657    });
2658
2659    // Create remote buffer
2660    let remote_buffer_a = project_remote
2661        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2662        .await
2663        .unwrap();
2664    let remote_unstaged_diff_a = project_remote
2665        .update(cx_b, |p, cx| {
2666            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2667        })
2668        .await
2669        .unwrap();
2670
2671    // Wait remote buffer to catch up to the new diff
2672    executor.run_until_parked();
2673    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2674        let buffer = remote_buffer_a.read(cx);
2675        assert_eq!(
2676            diff.base_text_string().as_deref(),
2677            Some(staged_text.as_str())
2678        );
2679        assert_hunks(
2680            diff.hunks_in_row_range(0..4, buffer, cx),
2681            buffer,
2682            &diff.base_text_string().unwrap(),
2683            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2684        );
2685    });
2686
2687    // Open uncommitted changes on the guest, without opening them on the host first
2688    let remote_uncommitted_diff_a = project_remote
2689        .update(cx_b, |p, cx| {
2690            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2691        })
2692        .await
2693        .unwrap();
2694    executor.run_until_parked();
2695    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2696        let buffer = remote_buffer_a.read(cx);
2697        assert_eq!(
2698            diff.base_text_string().as_deref(),
2699            Some(committed_text.as_str())
2700        );
2701        assert_hunks(
2702            diff.hunks_in_row_range(0..4, buffer, cx),
2703            buffer,
2704            &diff.base_text_string().unwrap(),
2705            &[(
2706                1..2,
2707                "TWO\n",
2708                "two\n",
2709                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2710            )],
2711        );
2712    });
2713
2714    // Update the index text of the open buffer
2715    client_a.fs().set_index_for_repo(
2716        Path::new("/dir/.git"),
2717        &[("a.txt".into(), new_staged_text.clone())],
2718    );
2719    client_a.fs().set_head_for_repo(
2720        Path::new("/dir/.git"),
2721        &[("a.txt".into(), new_committed_text.clone())],
2722    );
2723
2724    // Wait for buffer_local_a to receive it
2725    executor.run_until_parked();
2726    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2727        let buffer = buffer_local_a.read(cx);
2728        assert_eq!(
2729            diff.base_text_string().as_deref(),
2730            Some(new_staged_text.as_str())
2731        );
2732        assert_hunks(
2733            diff.hunks_in_row_range(0..4, buffer, cx),
2734            buffer,
2735            &diff.base_text_string().unwrap(),
2736            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2737        );
2738    });
2739
2740    // Guest receives index text update
2741    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2742        let buffer = remote_buffer_a.read(cx);
2743        assert_eq!(
2744            diff.base_text_string().as_deref(),
2745            Some(new_staged_text.as_str())
2746        );
2747        assert_hunks(
2748            diff.hunks_in_row_range(0..4, buffer, cx),
2749            buffer,
2750            &diff.base_text_string().unwrap(),
2751            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2752        );
2753    });
2754
2755    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2756        let buffer = remote_buffer_a.read(cx);
2757        assert_eq!(
2758            diff.base_text_string().as_deref(),
2759            Some(new_committed_text.as_str())
2760        );
2761        assert_hunks(
2762            diff.hunks_in_row_range(0..4, buffer, cx),
2763            buffer,
2764            &diff.base_text_string().unwrap(),
2765            &[(
2766                1..2,
2767                "TWO_HUNDRED\n",
2768                "two\n",
2769                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2770            )],
2771        );
2772    });
2773
2774    // Nested git dir
2775    let staged_text = "
2776        one
2777        three
2778    "
2779    .unindent();
2780
2781    let new_staged_text = "
2782        one
2783        two
2784    "
2785    .unindent();
2786
2787    client_a.fs().set_index_for_repo(
2788        Path::new("/dir/sub/.git"),
2789        &[("b.txt".into(), staged_text.clone())],
2790    );
2791
2792    // Create the buffer
2793    let buffer_local_b = project_local
2794        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2795        .await
2796        .unwrap();
2797    let local_unstaged_diff_b = project_local
2798        .update(cx_a, |p, cx| {
2799            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2800        })
2801        .await
2802        .unwrap();
2803
2804    // Wait for it to catch up to the new diff
2805    executor.run_until_parked();
2806    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2807        let buffer = buffer_local_b.read(cx);
2808        assert_eq!(
2809            diff.base_text_string().as_deref(),
2810            Some(staged_text.as_str())
2811        );
2812        assert_hunks(
2813            diff.hunks_in_row_range(0..4, buffer, cx),
2814            buffer,
2815            &diff.base_text_string().unwrap(),
2816            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2817        );
2818    });
2819
2820    // Create remote buffer
2821    let remote_buffer_b = project_remote
2822        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2823        .await
2824        .unwrap();
2825    let remote_unstaged_diff_b = project_remote
2826        .update(cx_b, |p, cx| {
2827            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2828        })
2829        .await
2830        .unwrap();
2831
2832    executor.run_until_parked();
2833    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2834        let buffer = remote_buffer_b.read(cx);
2835        assert_eq!(
2836            diff.base_text_string().as_deref(),
2837            Some(staged_text.as_str())
2838        );
2839        assert_hunks(
2840            diff.hunks_in_row_range(0..4, buffer, cx),
2841            buffer,
2842            &staged_text,
2843            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2844        );
2845    });
2846
2847    // Updatet the staged text
2848    client_a.fs().set_index_for_repo(
2849        Path::new("/dir/sub/.git"),
2850        &[("b.txt".into(), new_staged_text.clone())],
2851    );
2852
2853    // Wait for buffer_local_b to receive it
2854    executor.run_until_parked();
2855    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2856        let buffer = buffer_local_b.read(cx);
2857        assert_eq!(
2858            diff.base_text_string().as_deref(),
2859            Some(new_staged_text.as_str())
2860        );
2861        assert_hunks(
2862            diff.hunks_in_row_range(0..4, buffer, cx),
2863            buffer,
2864            &new_staged_text,
2865            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2866        );
2867    });
2868
2869    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2870        let buffer = remote_buffer_b.read(cx);
2871        assert_eq!(
2872            diff.base_text_string().as_deref(),
2873            Some(new_staged_text.as_str())
2874        );
2875        assert_hunks(
2876            diff.hunks_in_row_range(0..4, buffer, cx),
2877            buffer,
2878            &new_staged_text,
2879            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2880        );
2881    });
2882}
2883
2884#[gpui::test(iterations = 10)]
2885async fn test_git_branch_name(
2886    executor: BackgroundExecutor,
2887    cx_a: &mut TestAppContext,
2888    cx_b: &mut TestAppContext,
2889    cx_c: &mut TestAppContext,
2890) {
2891    let mut server = TestServer::start(executor.clone()).await;
2892    let client_a = server.create_client(cx_a, "user_a").await;
2893    let client_b = server.create_client(cx_b, "user_b").await;
2894    let client_c = server.create_client(cx_c, "user_c").await;
2895    server
2896        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2897        .await;
2898    let active_call_a = cx_a.read(ActiveCall::global);
2899
2900    client_a
2901        .fs()
2902        .insert_tree(
2903            "/dir",
2904            json!({
2905            ".git": {},
2906            }),
2907        )
2908        .await;
2909
2910    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2911    let project_id = active_call_a
2912        .update(cx_a, |call, cx| {
2913            call.share_project(project_local.clone(), cx)
2914        })
2915        .await
2916        .unwrap();
2917
2918    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2919    client_a
2920        .fs()
2921        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2922
2923    // Wait for it to catch up to the new branch
2924    executor.run_until_parked();
2925
2926    #[track_caller]
2927    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2928        let branch_name = branch_name.map(Into::into);
2929        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2930        assert_eq!(repositories.len(), 1);
2931        let repository = repositories[0].clone();
2932        assert_eq!(
2933            repository
2934                .read(cx)
2935                .branch
2936                .as_ref()
2937                .map(|branch| branch.name().to_owned()),
2938            branch_name
2939        )
2940    }
2941
2942    // Smoke test branch reading
2943
2944    project_local.read_with(cx_a, |project, cx| {
2945        assert_branch(Some("branch-1"), project, cx)
2946    });
2947
2948    project_remote.read_with(cx_b, |project, cx| {
2949        assert_branch(Some("branch-1"), project, cx)
2950    });
2951
2952    client_a
2953        .fs()
2954        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2955
2956    // Wait for buffer_local_a to receive it
2957    executor.run_until_parked();
2958
2959    // Smoke test branch reading
2960
2961    project_local.read_with(cx_a, |project, cx| {
2962        assert_branch(Some("branch-2"), project, cx)
2963    });
2964
2965    project_remote.read_with(cx_b, |project, cx| {
2966        assert_branch(Some("branch-2"), project, cx)
2967    });
2968
2969    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2970    executor.run_until_parked();
2971
2972    project_remote_c.read_with(cx_c, |project, cx| {
2973        assert_branch(Some("branch-2"), project, cx)
2974    });
2975}
2976
2977#[gpui::test]
2978async fn test_git_status_sync(
2979    executor: BackgroundExecutor,
2980    cx_a: &mut TestAppContext,
2981    cx_b: &mut TestAppContext,
2982    cx_c: &mut TestAppContext,
2983) {
2984    let mut server = TestServer::start(executor.clone()).await;
2985    let client_a = server.create_client(cx_a, "user_a").await;
2986    let client_b = server.create_client(cx_b, "user_b").await;
2987    let client_c = server.create_client(cx_c, "user_c").await;
2988    server
2989        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2990        .await;
2991    let active_call_a = cx_a.read(ActiveCall::global);
2992
2993    client_a
2994        .fs()
2995        .insert_tree(
2996            path!("/dir"),
2997            json!({
2998                ".git": {},
2999                "a.txt": "a",
3000                "b.txt": "b",
3001                "c.txt": "c",
3002            }),
3003        )
3004        .await;
3005
3006    // Initially, a.txt is uncommitted, but present in the index,
3007    // and b.txt is unmerged.
3008    client_a.fs().set_head_for_repo(
3009        path!("/dir/.git").as_ref(),
3010        &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
3011    );
3012    client_a.fs().set_index_for_repo(
3013        path!("/dir/.git").as_ref(),
3014        &[
3015            ("a.txt".into(), "".into()),
3016            ("b.txt".into(), "B".into()),
3017            ("c.txt".into(), "c".into()),
3018        ],
3019    );
3020    client_a.fs().set_unmerged_paths_for_repo(
3021        path!("/dir/.git").as_ref(),
3022        &[(
3023            "b.txt".into(),
3024            UnmergedStatus {
3025                first_head: UnmergedStatusCode::Updated,
3026                second_head: UnmergedStatusCode::Deleted,
3027            },
3028        )],
3029    );
3030
3031    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3032        index_status: StatusCode::Added,
3033        worktree_status: StatusCode::Modified,
3034    });
3035    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3036        first_head: UnmergedStatusCode::Updated,
3037        second_head: UnmergedStatusCode::Deleted,
3038    });
3039
3040    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3041    let project_id = active_call_a
3042        .update(cx_a, |call, cx| {
3043            call.share_project(project_local.clone(), cx)
3044        })
3045        .await
3046        .unwrap();
3047
3048    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3049
3050    // Wait for it to catch up to the new status
3051    executor.run_until_parked();
3052
3053    #[track_caller]
3054    fn assert_status(
3055        file: impl AsRef<Path>,
3056        status: Option<FileStatus>,
3057        project: &Project,
3058        cx: &App,
3059    ) {
3060        let file = file.as_ref();
3061        let repos = project
3062            .repositories(cx)
3063            .values()
3064            .cloned()
3065            .collect::<Vec<_>>();
3066        assert_eq!(repos.len(), 1);
3067        let repo = repos.into_iter().next().unwrap();
3068        assert_eq!(
3069            repo.read(cx)
3070                .status_for_path(&file.into())
3071                .map(|entry| entry.status),
3072            status
3073        );
3074    }
3075
3076    project_local.read_with(cx_a, |project, cx| {
3077        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3078        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3079        assert_status("c.txt", None, project, cx);
3080    });
3081
3082    project_remote.read_with(cx_b, |project, cx| {
3083        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3084        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3085        assert_status("c.txt", None, project, cx);
3086    });
3087
3088    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3089        index_status: StatusCode::Added,
3090        worktree_status: StatusCode::Unmodified,
3091    });
3092    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3093        index_status: StatusCode::Deleted,
3094        worktree_status: StatusCode::Added,
3095    });
3096    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3097        index_status: StatusCode::Unmodified,
3098        worktree_status: StatusCode::Modified,
3099    });
3100
3101    // Delete b.txt from the index, mark conflict as resolved,
3102    // and modify c.txt in the working copy.
3103    client_a.fs().set_index_for_repo(
3104        path!("/dir/.git").as_ref(),
3105        &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
3106    );
3107    client_a
3108        .fs()
3109        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3110    client_a
3111        .fs()
3112        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3113        .await
3114        .unwrap();
3115
3116    // Wait for buffer_local_a to receive it
3117    executor.run_until_parked();
3118
3119    // Smoke test status reading
3120    project_local.read_with(cx_a, |project, cx| {
3121        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3122        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3123        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3124    });
3125
3126    project_remote.read_with(cx_b, |project, cx| {
3127        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3128        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3129        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3130    });
3131
3132    // And synchronization while joining
3133    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3134    executor.run_until_parked();
3135
3136    project_remote_c.read_with(cx_c, |project, cx| {
3137        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3138        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3139        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3140    });
3141
3142    // Now remove the original git repository and check that collaborators are notified.
3143    client_a
3144        .fs()
3145        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3146        .await
3147        .unwrap();
3148
3149    executor.run_until_parked();
3150    project_remote.update(cx_b, |project, cx| {
3151        pretty_assertions::assert_eq!(
3152            project.git_store().read(cx).repo_snapshots(cx),
3153            HashMap::default()
3154        );
3155    });
3156    project_remote_c.update(cx_c, |project, cx| {
3157        pretty_assertions::assert_eq!(
3158            project.git_store().read(cx).repo_snapshots(cx),
3159            HashMap::default()
3160        );
3161    });
3162}
3163
3164#[gpui::test(iterations = 10)]
3165async fn test_fs_operations(
3166    executor: BackgroundExecutor,
3167    cx_a: &mut TestAppContext,
3168    cx_b: &mut TestAppContext,
3169) {
3170    let mut server = TestServer::start(executor.clone()).await;
3171    let client_a = server.create_client(cx_a, "user_a").await;
3172    let client_b = server.create_client(cx_b, "user_b").await;
3173    server
3174        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3175        .await;
3176    let active_call_a = cx_a.read(ActiveCall::global);
3177
3178    client_a
3179        .fs()
3180        .insert_tree(
3181            path!("/dir"),
3182            json!({
3183                "a.txt": "a-contents",
3184                "b.txt": "b-contents",
3185            }),
3186        )
3187        .await;
3188    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3189    let project_id = active_call_a
3190        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3191        .await
3192        .unwrap();
3193    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3194
3195    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3196    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3197
3198    let entry = project_b
3199        .update(cx_b, |project, cx| {
3200            project.create_entry((worktree_id, "c.txt"), false, cx)
3201        })
3202        .await
3203        .unwrap()
3204        .to_included()
3205        .unwrap();
3206
3207    worktree_a.read_with(cx_a, |worktree, _| {
3208        assert_eq!(
3209            worktree
3210                .paths()
3211                .map(|p| p.to_string_lossy())
3212                .collect::<Vec<_>>(),
3213            ["a.txt", "b.txt", "c.txt"]
3214        );
3215    });
3216
3217    worktree_b.read_with(cx_b, |worktree, _| {
3218        assert_eq!(
3219            worktree
3220                .paths()
3221                .map(|p| p.to_string_lossy())
3222                .collect::<Vec<_>>(),
3223            ["a.txt", "b.txt", "c.txt"]
3224        );
3225    });
3226
3227    project_b
3228        .update(cx_b, |project, cx| {
3229            project.rename_entry(entry.id, Path::new("d.txt"), cx)
3230        })
3231        .await
3232        .unwrap()
3233        .to_included()
3234        .unwrap();
3235
3236    worktree_a.read_with(cx_a, |worktree, _| {
3237        assert_eq!(
3238            worktree
3239                .paths()
3240                .map(|p| p.to_string_lossy())
3241                .collect::<Vec<_>>(),
3242            ["a.txt", "b.txt", "d.txt"]
3243        );
3244    });
3245
3246    worktree_b.read_with(cx_b, |worktree, _| {
3247        assert_eq!(
3248            worktree
3249                .paths()
3250                .map(|p| p.to_string_lossy())
3251                .collect::<Vec<_>>(),
3252            ["a.txt", "b.txt", "d.txt"]
3253        );
3254    });
3255
3256    let dir_entry = project_b
3257        .update(cx_b, |project, cx| {
3258            project.create_entry((worktree_id, "DIR"), true, cx)
3259        })
3260        .await
3261        .unwrap()
3262        .to_included()
3263        .unwrap();
3264
3265    worktree_a.read_with(cx_a, |worktree, _| {
3266        assert_eq!(
3267            worktree
3268                .paths()
3269                .map(|p| p.to_string_lossy())
3270                .collect::<Vec<_>>(),
3271            ["DIR", "a.txt", "b.txt", "d.txt"]
3272        );
3273    });
3274
3275    worktree_b.read_with(cx_b, |worktree, _| {
3276        assert_eq!(
3277            worktree
3278                .paths()
3279                .map(|p| p.to_string_lossy())
3280                .collect::<Vec<_>>(),
3281            ["DIR", "a.txt", "b.txt", "d.txt"]
3282        );
3283    });
3284
3285    project_b
3286        .update(cx_b, |project, cx| {
3287            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
3288        })
3289        .await
3290        .unwrap()
3291        .to_included()
3292        .unwrap();
3293
3294    project_b
3295        .update(cx_b, |project, cx| {
3296            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
3297        })
3298        .await
3299        .unwrap()
3300        .to_included()
3301        .unwrap();
3302
3303    project_b
3304        .update(cx_b, |project, cx| {
3305            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
3306        })
3307        .await
3308        .unwrap()
3309        .to_included()
3310        .unwrap();
3311
3312    worktree_a.read_with(cx_a, |worktree, _| {
3313        assert_eq!(
3314            worktree
3315                .paths()
3316                .map(|p| p.to_string_lossy())
3317                .collect::<Vec<_>>(),
3318            [
3319                separator!("DIR"),
3320                separator!("DIR/SUBDIR"),
3321                separator!("DIR/SUBDIR/f.txt"),
3322                separator!("DIR/e.txt"),
3323                separator!("a.txt"),
3324                separator!("b.txt"),
3325                separator!("d.txt")
3326            ]
3327        );
3328    });
3329
3330    worktree_b.read_with(cx_b, |worktree, _| {
3331        assert_eq!(
3332            worktree
3333                .paths()
3334                .map(|p| p.to_string_lossy())
3335                .collect::<Vec<_>>(),
3336            [
3337                separator!("DIR"),
3338                separator!("DIR/SUBDIR"),
3339                separator!("DIR/SUBDIR/f.txt"),
3340                separator!("DIR/e.txt"),
3341                separator!("a.txt"),
3342                separator!("b.txt"),
3343                separator!("d.txt")
3344            ]
3345        );
3346    });
3347
3348    project_b
3349        .update(cx_b, |project, cx| {
3350            project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
3351        })
3352        .await
3353        .unwrap()
3354        .unwrap();
3355
3356    worktree_a.read_with(cx_a, |worktree, _| {
3357        assert_eq!(
3358            worktree
3359                .paths()
3360                .map(|p| p.to_string_lossy())
3361                .collect::<Vec<_>>(),
3362            [
3363                separator!("DIR"),
3364                separator!("DIR/SUBDIR"),
3365                separator!("DIR/SUBDIR/f.txt"),
3366                separator!("DIR/e.txt"),
3367                separator!("a.txt"),
3368                separator!("b.txt"),
3369                separator!("d.txt"),
3370                separator!("f.txt")
3371            ]
3372        );
3373    });
3374
3375    worktree_b.read_with(cx_b, |worktree, _| {
3376        assert_eq!(
3377            worktree
3378                .paths()
3379                .map(|p| p.to_string_lossy())
3380                .collect::<Vec<_>>(),
3381            [
3382                separator!("DIR"),
3383                separator!("DIR/SUBDIR"),
3384                separator!("DIR/SUBDIR/f.txt"),
3385                separator!("DIR/e.txt"),
3386                separator!("a.txt"),
3387                separator!("b.txt"),
3388                separator!("d.txt"),
3389                separator!("f.txt")
3390            ]
3391        );
3392    });
3393
3394    project_b
3395        .update(cx_b, |project, cx| {
3396            project.delete_entry(dir_entry.id, false, cx).unwrap()
3397        })
3398        .await
3399        .unwrap();
3400    executor.run_until_parked();
3401
3402    worktree_a.read_with(cx_a, |worktree, _| {
3403        assert_eq!(
3404            worktree
3405                .paths()
3406                .map(|p| p.to_string_lossy())
3407                .collect::<Vec<_>>(),
3408            ["a.txt", "b.txt", "d.txt", "f.txt"]
3409        );
3410    });
3411
3412    worktree_b.read_with(cx_b, |worktree, _| {
3413        assert_eq!(
3414            worktree
3415                .paths()
3416                .map(|p| p.to_string_lossy())
3417                .collect::<Vec<_>>(),
3418            ["a.txt", "b.txt", "d.txt", "f.txt"]
3419        );
3420    });
3421
3422    project_b
3423        .update(cx_b, |project, cx| {
3424            project.delete_entry(entry.id, false, cx).unwrap()
3425        })
3426        .await
3427        .unwrap();
3428
3429    worktree_a.read_with(cx_a, |worktree, _| {
3430        assert_eq!(
3431            worktree
3432                .paths()
3433                .map(|p| p.to_string_lossy())
3434                .collect::<Vec<_>>(),
3435            ["a.txt", "b.txt", "f.txt"]
3436        );
3437    });
3438
3439    worktree_b.read_with(cx_b, |worktree, _| {
3440        assert_eq!(
3441            worktree
3442                .paths()
3443                .map(|p| p.to_string_lossy())
3444                .collect::<Vec<_>>(),
3445            ["a.txt", "b.txt", "f.txt"]
3446        );
3447    });
3448}
3449
3450#[gpui::test(iterations = 10)]
3451async fn test_local_settings(
3452    executor: BackgroundExecutor,
3453    cx_a: &mut TestAppContext,
3454    cx_b: &mut TestAppContext,
3455) {
3456    let mut server = TestServer::start(executor.clone()).await;
3457    let client_a = server.create_client(cx_a, "user_a").await;
3458    let client_b = server.create_client(cx_b, "user_b").await;
3459    server
3460        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3461        .await;
3462    let active_call_a = cx_a.read(ActiveCall::global);
3463
3464    // As client A, open a project that contains some local settings files
3465    client_a
3466        .fs()
3467        .insert_tree(
3468            "/dir",
3469            json!({
3470                ".zed": {
3471                    "settings.json": r#"{ "tab_size": 2 }"#
3472                },
3473                "a": {
3474                    ".zed": {
3475                        "settings.json": r#"{ "tab_size": 8 }"#
3476                    },
3477                    "a.txt": "a-contents",
3478                },
3479                "b": {
3480                    "b.txt": "b-contents",
3481                }
3482            }),
3483        )
3484        .await;
3485    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3486    executor.run_until_parked();
3487    let project_id = active_call_a
3488        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3489        .await
3490        .unwrap();
3491    executor.run_until_parked();
3492
3493    // As client B, join that project and observe the local settings.
3494    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3495
3496    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3497    executor.run_until_parked();
3498    cx_b.read(|cx| {
3499        let store = cx.global::<SettingsStore>();
3500        assert_eq!(
3501            store
3502                .local_settings(worktree_b.read(cx).id())
3503                .collect::<Vec<_>>(),
3504            &[
3505                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
3506                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3507            ]
3508        )
3509    });
3510
3511    // As client A, update a settings file. As Client B, see the changed settings.
3512    client_a
3513        .fs()
3514        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3515        .await;
3516    executor.run_until_parked();
3517    cx_b.read(|cx| {
3518        let store = cx.global::<SettingsStore>();
3519        assert_eq!(
3520            store
3521                .local_settings(worktree_b.read(cx).id())
3522                .collect::<Vec<_>>(),
3523            &[
3524                (Path::new("").into(), r#"{}"#.to_string()),
3525                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3526            ]
3527        )
3528    });
3529
3530    // As client A, create and remove some settings files. As client B, see the changed settings.
3531    client_a
3532        .fs()
3533        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3534        .await
3535        .unwrap();
3536    client_a
3537        .fs()
3538        .create_dir("/dir/b/.zed".as_ref())
3539        .await
3540        .unwrap();
3541    client_a
3542        .fs()
3543        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3544        .await;
3545    executor.run_until_parked();
3546    cx_b.read(|cx| {
3547        let store = cx.global::<SettingsStore>();
3548        assert_eq!(
3549            store
3550                .local_settings(worktree_b.read(cx).id())
3551                .collect::<Vec<_>>(),
3552            &[
3553                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3554                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
3555            ]
3556        )
3557    });
3558
3559    // As client B, disconnect.
3560    server.forbid_connections();
3561    server.disconnect_client(client_b.peer_id().unwrap());
3562
3563    // As client A, change and remove settings files while client B is disconnected.
3564    client_a
3565        .fs()
3566        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3567        .await;
3568    client_a
3569        .fs()
3570        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3571        .await
3572        .unwrap();
3573    executor.run_until_parked();
3574
3575    // As client B, reconnect and see the changed settings.
3576    server.allow_connections();
3577    executor.advance_clock(RECEIVE_TIMEOUT);
3578    cx_b.read(|cx| {
3579        let store = cx.global::<SettingsStore>();
3580        assert_eq!(
3581            store
3582                .local_settings(worktree_b.read(cx).id())
3583                .collect::<Vec<_>>(),
3584            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
3585        )
3586    });
3587}
3588
3589#[gpui::test(iterations = 10)]
3590async fn test_buffer_conflict_after_save(
3591    executor: BackgroundExecutor,
3592    cx_a: &mut TestAppContext,
3593    cx_b: &mut TestAppContext,
3594) {
3595    let mut server = TestServer::start(executor.clone()).await;
3596    let client_a = server.create_client(cx_a, "user_a").await;
3597    let client_b = server.create_client(cx_b, "user_b").await;
3598    server
3599        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3600        .await;
3601    let active_call_a = cx_a.read(ActiveCall::global);
3602
3603    client_a
3604        .fs()
3605        .insert_tree(
3606            path!("/dir"),
3607            json!({
3608                "a.txt": "a-contents",
3609            }),
3610        )
3611        .await;
3612    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3613    let project_id = active_call_a
3614        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3615        .await
3616        .unwrap();
3617    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3618
3619    // Open a buffer as client B
3620    let buffer_b = project_b
3621        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3622        .await
3623        .unwrap();
3624
3625    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3626
3627    buffer_b.read_with(cx_b, |buf, _| {
3628        assert!(buf.is_dirty());
3629        assert!(!buf.has_conflict());
3630    });
3631
3632    project_b
3633        .update(cx_b, |project, cx| {
3634            project.save_buffer(buffer_b.clone(), cx)
3635        })
3636        .await
3637        .unwrap();
3638
3639    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3640
3641    buffer_b.read_with(cx_b, |buf, _| {
3642        assert!(!buf.has_conflict());
3643    });
3644
3645    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3646
3647    buffer_b.read_with(cx_b, |buf, _| {
3648        assert!(buf.is_dirty());
3649        assert!(!buf.has_conflict());
3650    });
3651}
3652
3653#[gpui::test(iterations = 10)]
3654async fn test_buffer_reloading(
3655    executor: BackgroundExecutor,
3656    cx_a: &mut TestAppContext,
3657    cx_b: &mut TestAppContext,
3658) {
3659    let mut server = TestServer::start(executor.clone()).await;
3660    let client_a = server.create_client(cx_a, "user_a").await;
3661    let client_b = server.create_client(cx_b, "user_b").await;
3662    server
3663        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3664        .await;
3665    let active_call_a = cx_a.read(ActiveCall::global);
3666
3667    client_a
3668        .fs()
3669        .insert_tree(
3670            path!("/dir"),
3671            json!({
3672                "a.txt": "a\nb\nc",
3673            }),
3674        )
3675        .await;
3676    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3677    let project_id = active_call_a
3678        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3679        .await
3680        .unwrap();
3681    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3682
3683    // Open a buffer as client B
3684    let buffer_b = project_b
3685        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3686        .await
3687        .unwrap();
3688
3689    buffer_b.read_with(cx_b, |buf, _| {
3690        assert!(!buf.is_dirty());
3691        assert!(!buf.has_conflict());
3692        assert_eq!(buf.line_ending(), LineEnding::Unix);
3693    });
3694
3695    let new_contents = Rope::from("d\ne\nf");
3696    client_a
3697        .fs()
3698        .save(
3699            path!("/dir/a.txt").as_ref(),
3700            &new_contents,
3701            LineEnding::Windows,
3702        )
3703        .await
3704        .unwrap();
3705
3706    executor.run_until_parked();
3707
3708    buffer_b.read_with(cx_b, |buf, _| {
3709        assert_eq!(buf.text(), new_contents.to_string());
3710        assert!(!buf.is_dirty());
3711        assert!(!buf.has_conflict());
3712        assert_eq!(buf.line_ending(), LineEnding::Windows);
3713    });
3714}
3715
3716#[gpui::test(iterations = 10)]
3717async fn test_editing_while_guest_opens_buffer(
3718    executor: BackgroundExecutor,
3719    cx_a: &mut TestAppContext,
3720    cx_b: &mut TestAppContext,
3721) {
3722    let mut server = TestServer::start(executor.clone()).await;
3723    let client_a = server.create_client(cx_a, "user_a").await;
3724    let client_b = server.create_client(cx_b, "user_b").await;
3725    server
3726        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3727        .await;
3728    let active_call_a = cx_a.read(ActiveCall::global);
3729
3730    client_a
3731        .fs()
3732        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3733        .await;
3734    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3735    let project_id = active_call_a
3736        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3737        .await
3738        .unwrap();
3739    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3740
3741    // Open a buffer as client A
3742    let buffer_a = project_a
3743        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3744        .await
3745        .unwrap();
3746
3747    // Start opening the same buffer as client B
3748    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3749    let buffer_b = cx_b.executor().spawn(open_buffer);
3750
3751    // Edit the buffer as client A while client B is still opening it.
3752    cx_b.executor().simulate_random_delay().await;
3753    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3754    cx_b.executor().simulate_random_delay().await;
3755    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3756
3757    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3758    let buffer_b = buffer_b.await.unwrap();
3759    executor.run_until_parked();
3760
3761    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3762}
3763
3764#[gpui::test(iterations = 10)]
3765async fn test_leaving_worktree_while_opening_buffer(
3766    executor: BackgroundExecutor,
3767    cx_a: &mut TestAppContext,
3768    cx_b: &mut TestAppContext,
3769) {
3770    let mut server = TestServer::start(executor.clone()).await;
3771    let client_a = server.create_client(cx_a, "user_a").await;
3772    let client_b = server.create_client(cx_b, "user_b").await;
3773    server
3774        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3775        .await;
3776    let active_call_a = cx_a.read(ActiveCall::global);
3777
3778    client_a
3779        .fs()
3780        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3781        .await;
3782    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3783    let project_id = active_call_a
3784        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3785        .await
3786        .unwrap();
3787    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3788
3789    // See that a guest has joined as client A.
3790    executor.run_until_parked();
3791
3792    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3793
3794    // Begin opening a buffer as client B, but leave the project before the open completes.
3795    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3796    let buffer_b = cx_b.executor().spawn(open_buffer);
3797    cx_b.update(|_| drop(project_b));
3798    drop(buffer_b);
3799
3800    // See that the guest has left.
3801    executor.run_until_parked();
3802
3803    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3804}
3805
3806#[gpui::test(iterations = 10)]
3807async fn test_canceling_buffer_opening(
3808    executor: BackgroundExecutor,
3809    cx_a: &mut TestAppContext,
3810    cx_b: &mut TestAppContext,
3811) {
3812    let mut server = TestServer::start(executor.clone()).await;
3813    let client_a = server.create_client(cx_a, "user_a").await;
3814    let client_b = server.create_client(cx_b, "user_b").await;
3815    server
3816        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3817        .await;
3818    let active_call_a = cx_a.read(ActiveCall::global);
3819
3820    client_a
3821        .fs()
3822        .insert_tree(
3823            "/dir",
3824            json!({
3825                "a.txt": "abc",
3826            }),
3827        )
3828        .await;
3829    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3830    let project_id = active_call_a
3831        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3832        .await
3833        .unwrap();
3834    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3835
3836    let buffer_a = project_a
3837        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3838        .await
3839        .unwrap();
3840
3841    // Open a buffer as client B but cancel after a random amount of time.
3842    let buffer_b = project_b.update(cx_b, |p, cx| {
3843        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3844    });
3845    executor.simulate_random_delay().await;
3846    drop(buffer_b);
3847
3848    // Try opening the same buffer again as client B, and ensure we can
3849    // still do it despite the cancellation above.
3850    let buffer_b = project_b
3851        .update(cx_b, |p, cx| {
3852            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3853        })
3854        .await
3855        .unwrap();
3856
3857    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3858}
3859
3860#[gpui::test(iterations = 10)]
3861async fn test_leaving_project(
3862    executor: BackgroundExecutor,
3863    cx_a: &mut TestAppContext,
3864    cx_b: &mut TestAppContext,
3865    cx_c: &mut TestAppContext,
3866) {
3867    let mut server = TestServer::start(executor.clone()).await;
3868    let client_a = server.create_client(cx_a, "user_a").await;
3869    let client_b = server.create_client(cx_b, "user_b").await;
3870    let client_c = server.create_client(cx_c, "user_c").await;
3871    server
3872        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3873        .await;
3874    let active_call_a = cx_a.read(ActiveCall::global);
3875
3876    client_a
3877        .fs()
3878        .insert_tree(
3879            "/a",
3880            json!({
3881                "a.txt": "a-contents",
3882                "b.txt": "b-contents",
3883            }),
3884        )
3885        .await;
3886    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3887    let project_id = active_call_a
3888        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3889        .await
3890        .unwrap();
3891    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3892    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3893
3894    // Client A sees that a guest has joined.
3895    executor.run_until_parked();
3896
3897    project_a.read_with(cx_a, |project, _| {
3898        assert_eq!(project.collaborators().len(), 2);
3899    });
3900
3901    project_b1.read_with(cx_b, |project, _| {
3902        assert_eq!(project.collaborators().len(), 2);
3903    });
3904
3905    project_c.read_with(cx_c, |project, _| {
3906        assert_eq!(project.collaborators().len(), 2);
3907    });
3908
3909    // Client B opens a buffer.
3910    let buffer_b1 = project_b1
3911        .update(cx_b, |project, cx| {
3912            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3913            project.open_buffer((worktree_id, "a.txt"), cx)
3914        })
3915        .await
3916        .unwrap();
3917
3918    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3919
3920    // Drop client B's project and ensure client A and client C observe client B leaving.
3921    cx_b.update(|_| drop(project_b1));
3922    executor.run_until_parked();
3923
3924    project_a.read_with(cx_a, |project, _| {
3925        assert_eq!(project.collaborators().len(), 1);
3926    });
3927
3928    project_c.read_with(cx_c, |project, _| {
3929        assert_eq!(project.collaborators().len(), 1);
3930    });
3931
3932    // Client B re-joins the project and can open buffers as before.
3933    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3934    executor.run_until_parked();
3935
3936    project_a.read_with(cx_a, |project, _| {
3937        assert_eq!(project.collaborators().len(), 2);
3938    });
3939
3940    project_b2.read_with(cx_b, |project, _| {
3941        assert_eq!(project.collaborators().len(), 2);
3942    });
3943
3944    project_c.read_with(cx_c, |project, _| {
3945        assert_eq!(project.collaborators().len(), 2);
3946    });
3947
3948    let buffer_b2 = project_b2
3949        .update(cx_b, |project, cx| {
3950            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3951            project.open_buffer((worktree_id, "a.txt"), cx)
3952        })
3953        .await
3954        .unwrap();
3955
3956    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3957
3958    project_a.read_with(cx_a, |project, _| {
3959        assert_eq!(project.collaborators().len(), 2);
3960    });
3961
3962    // Drop client B's connection and ensure client A and client C observe client B leaving.
3963    client_b.disconnect(&cx_b.to_async());
3964    executor.advance_clock(RECONNECT_TIMEOUT);
3965
3966    project_a.read_with(cx_a, |project, _| {
3967        assert_eq!(project.collaborators().len(), 1);
3968    });
3969
3970    project_b2.read_with(cx_b, |project, cx| {
3971        assert!(project.is_disconnected(cx));
3972    });
3973
3974    project_c.read_with(cx_c, |project, _| {
3975        assert_eq!(project.collaborators().len(), 1);
3976    });
3977
3978    // Client B can't join the project, unless they re-join the room.
3979    cx_b.spawn(|cx| {
3980        Project::in_room(
3981            project_id,
3982            client_b.app_state.client.clone(),
3983            client_b.user_store().clone(),
3984            client_b.language_registry().clone(),
3985            FakeFs::new(cx.background_executor().clone()),
3986            cx,
3987        )
3988    })
3989    .await
3990    .unwrap_err();
3991
3992    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
3993    client_c.wait_for_current_user(cx_c).await;
3994    server.forbid_connections();
3995    server.disconnect_client(client_c.peer_id().unwrap());
3996    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
3997    executor.run_until_parked();
3998
3999    project_a.read_with(cx_a, |project, _| {
4000        assert_eq!(project.collaborators().len(), 0);
4001    });
4002
4003    project_b2.read_with(cx_b, |project, cx| {
4004        assert!(project.is_disconnected(cx));
4005    });
4006
4007    project_c.read_with(cx_c, |project, cx| {
4008        assert!(project.is_disconnected(cx));
4009    });
4010}
4011
4012#[gpui::test(iterations = 10)]
4013async fn test_collaborating_with_diagnostics(
4014    executor: BackgroundExecutor,
4015    cx_a: &mut TestAppContext,
4016    cx_b: &mut TestAppContext,
4017    cx_c: &mut TestAppContext,
4018) {
4019    let mut server = TestServer::start(executor.clone()).await;
4020    let client_a = server.create_client(cx_a, "user_a").await;
4021    let client_b = server.create_client(cx_b, "user_b").await;
4022    let client_c = server.create_client(cx_c, "user_c").await;
4023    server
4024        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4025        .await;
4026    let active_call_a = cx_a.read(ActiveCall::global);
4027
4028    client_a.language_registry().add(Arc::new(Language::new(
4029        LanguageConfig {
4030            name: "Rust".into(),
4031            matcher: LanguageMatcher {
4032                path_suffixes: vec!["rs".to_string()],
4033                ..Default::default()
4034            },
4035            ..Default::default()
4036        },
4037        Some(tree_sitter_rust::LANGUAGE.into()),
4038    )));
4039    let mut fake_language_servers = client_a
4040        .language_registry()
4041        .register_fake_lsp("Rust", Default::default());
4042
4043    // Share a project as client A
4044    client_a
4045        .fs()
4046        .insert_tree(
4047            path!("/a"),
4048            json!({
4049                "a.rs": "let one = two",
4050                "other.rs": "",
4051            }),
4052        )
4053        .await;
4054    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4055
4056    // Cause the language server to start.
4057    let _buffer = project_a
4058        .update(cx_a, |project, cx| {
4059            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4060        })
4061        .await
4062        .unwrap();
4063
4064    // Simulate a language server reporting errors for a file.
4065    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4066    fake_language_server
4067        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4068        .await;
4069    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4070        &lsp::PublishDiagnosticsParams {
4071            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4072            version: None,
4073            diagnostics: vec![lsp::Diagnostic {
4074                severity: Some(lsp::DiagnosticSeverity::WARNING),
4075                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4076                message: "message 0".to_string(),
4077                ..Default::default()
4078            }],
4079        },
4080    );
4081
4082    // Client A shares the project and, simultaneously, the language server
4083    // publishes a diagnostic. This is done to ensure that the server always
4084    // observes the latest diagnostics for a worktree.
4085    let project_id = active_call_a
4086        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4087        .await
4088        .unwrap();
4089    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4090        &lsp::PublishDiagnosticsParams {
4091            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4092            version: None,
4093            diagnostics: vec![lsp::Diagnostic {
4094                severity: Some(lsp::DiagnosticSeverity::ERROR),
4095                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4096                message: "message 1".to_string(),
4097                ..Default::default()
4098            }],
4099        },
4100    );
4101
4102    // Join the worktree as client B.
4103    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4104
4105    // Wait for server to see the diagnostics update.
4106    executor.run_until_parked();
4107
4108    // Ensure client B observes the new diagnostics.
4109
4110    project_b.read_with(cx_b, |project, cx| {
4111        assert_eq!(
4112            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4113            &[(
4114                ProjectPath {
4115                    worktree_id,
4116                    path: Arc::from(Path::new("a.rs")),
4117                },
4118                LanguageServerId(0),
4119                DiagnosticSummary {
4120                    error_count: 1,
4121                    warning_count: 0,
4122                },
4123            )]
4124        )
4125    });
4126
4127    // Join project as client C and observe the diagnostics.
4128    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4129    executor.run_until_parked();
4130    let project_c_diagnostic_summaries =
4131        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4132            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4133        })));
4134    project_c.update(cx_c, |_, cx| {
4135        let summaries = project_c_diagnostic_summaries.clone();
4136        cx.subscribe(&project_c, {
4137            move |p, _, event, cx| {
4138                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4139                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4140                }
4141            }
4142        })
4143        .detach();
4144    });
4145
4146    executor.run_until_parked();
4147    assert_eq!(
4148        project_c_diagnostic_summaries.borrow().as_slice(),
4149        &[(
4150            ProjectPath {
4151                worktree_id,
4152                path: Arc::from(Path::new("a.rs")),
4153            },
4154            LanguageServerId(0),
4155            DiagnosticSummary {
4156                error_count: 1,
4157                warning_count: 0,
4158            },
4159        )]
4160    );
4161
4162    // Simulate a language server reporting more errors for a file.
4163    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4164        &lsp::PublishDiagnosticsParams {
4165            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4166            version: None,
4167            diagnostics: vec![
4168                lsp::Diagnostic {
4169                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4170                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4171                    message: "message 1".to_string(),
4172                    ..Default::default()
4173                },
4174                lsp::Diagnostic {
4175                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4176                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4177                    message: "message 2".to_string(),
4178                    ..Default::default()
4179                },
4180            ],
4181        },
4182    );
4183
4184    // Clients B and C get the updated summaries
4185    executor.run_until_parked();
4186
4187    project_b.read_with(cx_b, |project, cx| {
4188        assert_eq!(
4189            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4190            [(
4191                ProjectPath {
4192                    worktree_id,
4193                    path: Arc::from(Path::new("a.rs")),
4194                },
4195                LanguageServerId(0),
4196                DiagnosticSummary {
4197                    error_count: 1,
4198                    warning_count: 1,
4199                },
4200            )]
4201        );
4202    });
4203
4204    project_c.read_with(cx_c, |project, cx| {
4205        assert_eq!(
4206            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4207            [(
4208                ProjectPath {
4209                    worktree_id,
4210                    path: Arc::from(Path::new("a.rs")),
4211                },
4212                LanguageServerId(0),
4213                DiagnosticSummary {
4214                    error_count: 1,
4215                    warning_count: 1,
4216                },
4217            )]
4218        );
4219    });
4220
4221    // Open the file with the errors on client B. They should be present.
4222    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4223    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4224
4225    buffer_b.read_with(cx_b, |buffer, _| {
4226        assert_eq!(
4227            buffer
4228                .snapshot()
4229                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4230                .collect::<Vec<_>>(),
4231            &[
4232                DiagnosticEntry {
4233                    range: Point::new(0, 4)..Point::new(0, 7),
4234                    diagnostic: Diagnostic {
4235                        group_id: 2,
4236                        message: "message 1".to_string(),
4237                        severity: lsp::DiagnosticSeverity::ERROR,
4238                        is_primary: true,
4239                        ..Default::default()
4240                    }
4241                },
4242                DiagnosticEntry {
4243                    range: Point::new(0, 10)..Point::new(0, 13),
4244                    diagnostic: Diagnostic {
4245                        group_id: 3,
4246                        severity: lsp::DiagnosticSeverity::WARNING,
4247                        message: "message 2".to_string(),
4248                        is_primary: true,
4249                        ..Default::default()
4250                    }
4251                }
4252            ]
4253        );
4254    });
4255
4256    // Simulate a language server reporting no errors for a file.
4257    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4258        &lsp::PublishDiagnosticsParams {
4259            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4260            version: None,
4261            diagnostics: vec![],
4262        },
4263    );
4264    executor.run_until_parked();
4265
4266    project_a.read_with(cx_a, |project, cx| {
4267        assert_eq!(
4268            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4269            []
4270        )
4271    });
4272
4273    project_b.read_with(cx_b, |project, cx| {
4274        assert_eq!(
4275            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4276            []
4277        )
4278    });
4279
4280    project_c.read_with(cx_c, |project, cx| {
4281        assert_eq!(
4282            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4283            []
4284        )
4285    });
4286}
4287
4288#[gpui::test(iterations = 10)]
4289async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4290    executor: BackgroundExecutor,
4291    cx_a: &mut TestAppContext,
4292    cx_b: &mut TestAppContext,
4293) {
4294    let mut server = TestServer::start(executor.clone()).await;
4295    let client_a = server.create_client(cx_a, "user_a").await;
4296    let client_b = server.create_client(cx_b, "user_b").await;
4297    server
4298        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4299        .await;
4300
4301    client_a.language_registry().add(rust_lang());
4302    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4303        "Rust",
4304        FakeLspAdapter {
4305            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4306            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4307            ..Default::default()
4308        },
4309    );
4310
4311    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4312    client_a
4313        .fs()
4314        .insert_tree(
4315            path!("/test"),
4316            json!({
4317                "one.rs": "const ONE: usize = 1;",
4318                "two.rs": "const TWO: usize = 2;",
4319                "three.rs": "const THREE: usize = 3;",
4320                "four.rs": "const FOUR: usize = 3;",
4321                "five.rs": "const FIVE: usize = 3;",
4322            }),
4323        )
4324        .await;
4325
4326    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4327
4328    // Share a project as client A
4329    let active_call_a = cx_a.read(ActiveCall::global);
4330    let project_id = active_call_a
4331        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4332        .await
4333        .unwrap();
4334
4335    // Join the project as client B and open all three files.
4336    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4337    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4338        project_b.update(cx_b, |p, cx| {
4339            p.open_buffer_with_lsp((worktree_id, file_name), cx)
4340        })
4341    }))
4342    .await
4343    .unwrap();
4344
4345    // Simulate a language server reporting errors for a file.
4346    let fake_language_server = fake_language_servers.next().await.unwrap();
4347    fake_language_server
4348        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
4349            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4350        })
4351        .await
4352        .into_response()
4353        .unwrap();
4354    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4355        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4356        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4357            lsp::WorkDoneProgressBegin {
4358                title: "Progress Began".into(),
4359                ..Default::default()
4360            },
4361        )),
4362    });
4363    for file_name in file_names {
4364        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4365            &lsp::PublishDiagnosticsParams {
4366                uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4367                version: None,
4368                diagnostics: vec![lsp::Diagnostic {
4369                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4370                    source: Some("the-disk-based-diagnostics-source".into()),
4371                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4372                    message: "message one".to_string(),
4373                    ..Default::default()
4374                }],
4375            },
4376        );
4377    }
4378    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4379        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4380        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4381            lsp::WorkDoneProgressEnd { message: None },
4382        )),
4383    });
4384
4385    // When the "disk base diagnostics finished" message is received, the buffers'
4386    // diagnostics are expected to be present.
4387    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4388    project_b.update(cx_b, {
4389        let project_b = project_b.clone();
4390        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4391        move |_, cx| {
4392            cx.subscribe(&project_b, move |_, _, event, cx| {
4393                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4394                    disk_based_diagnostics_finished.store(true, SeqCst);
4395                    for (buffer, _) in &guest_buffers {
4396                        assert_eq!(
4397                            buffer
4398                                .read(cx)
4399                                .snapshot()
4400                                .diagnostics_in_range::<_, usize>(0..5, false)
4401                                .count(),
4402                            1,
4403                            "expected a diagnostic for buffer {:?}",
4404                            buffer.read(cx).file().unwrap().path(),
4405                        );
4406                    }
4407                }
4408            })
4409            .detach();
4410        }
4411    });
4412
4413    executor.run_until_parked();
4414    assert!(disk_based_diagnostics_finished.load(SeqCst));
4415}
4416
4417#[gpui::test(iterations = 10)]
4418async fn test_reloading_buffer_manually(
4419    executor: BackgroundExecutor,
4420    cx_a: &mut TestAppContext,
4421    cx_b: &mut TestAppContext,
4422) {
4423    let mut server = TestServer::start(executor.clone()).await;
4424    let client_a = server.create_client(cx_a, "user_a").await;
4425    let client_b = server.create_client(cx_b, "user_b").await;
4426    server
4427        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4428        .await;
4429    let active_call_a = cx_a.read(ActiveCall::global);
4430
4431    client_a
4432        .fs()
4433        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4434        .await;
4435    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4436    let buffer_a = project_a
4437        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4438        .await
4439        .unwrap();
4440    let project_id = active_call_a
4441        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4442        .await
4443        .unwrap();
4444
4445    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4446
4447    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4448    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4449    buffer_b.update(cx_b, |buffer, cx| {
4450        buffer.edit([(4..7, "six")], None, cx);
4451        buffer.edit([(10..11, "6")], None, cx);
4452        assert_eq!(buffer.text(), "let six = 6;");
4453        assert!(buffer.is_dirty());
4454        assert!(!buffer.has_conflict());
4455    });
4456    executor.run_until_parked();
4457
4458    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4459
4460    client_a
4461        .fs()
4462        .save(
4463            path!("/a/a.rs").as_ref(),
4464            &Rope::from("let seven = 7;"),
4465            LineEnding::Unix,
4466        )
4467        .await
4468        .unwrap();
4469    executor.run_until_parked();
4470
4471    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4472
4473    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4474
4475    project_b
4476        .update(cx_b, |project, cx| {
4477            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4478        })
4479        .await
4480        .unwrap();
4481
4482    buffer_a.read_with(cx_a, |buffer, _| {
4483        assert_eq!(buffer.text(), "let seven = 7;");
4484        assert!(!buffer.is_dirty());
4485        assert!(!buffer.has_conflict());
4486    });
4487
4488    buffer_b.read_with(cx_b, |buffer, _| {
4489        assert_eq!(buffer.text(), "let seven = 7;");
4490        assert!(!buffer.is_dirty());
4491        assert!(!buffer.has_conflict());
4492    });
4493
4494    buffer_a.update(cx_a, |buffer, cx| {
4495        // Undoing on the host is a no-op when the reload was initiated by the guest.
4496        buffer.undo(cx);
4497        assert_eq!(buffer.text(), "let seven = 7;");
4498        assert!(!buffer.is_dirty());
4499        assert!(!buffer.has_conflict());
4500    });
4501    buffer_b.update(cx_b, |buffer, cx| {
4502        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4503        buffer.undo(cx);
4504        assert_eq!(buffer.text(), "let six = 6;");
4505        assert!(buffer.is_dirty());
4506        assert!(!buffer.has_conflict());
4507    });
4508}
4509
4510#[gpui::test(iterations = 10)]
4511async fn test_formatting_buffer(
4512    executor: BackgroundExecutor,
4513    cx_a: &mut TestAppContext,
4514    cx_b: &mut TestAppContext,
4515) {
4516    let mut server = TestServer::start(executor.clone()).await;
4517    let client_a = server.create_client(cx_a, "user_a").await;
4518    let client_b = server.create_client(cx_b, "user_b").await;
4519    server
4520        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4521        .await;
4522    let active_call_a = cx_a.read(ActiveCall::global);
4523
4524    client_a.language_registry().add(rust_lang());
4525    let mut fake_language_servers = client_a
4526        .language_registry()
4527        .register_fake_lsp("Rust", FakeLspAdapter::default());
4528
4529    // Here we insert a fake tree with a directory that exists on disk. This is needed
4530    // because later we'll invoke a command, which requires passing a working directory
4531    // that points to a valid location on disk.
4532    let directory = env::current_dir().unwrap();
4533    client_a
4534        .fs()
4535        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4536        .await;
4537    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4538    let project_id = active_call_a
4539        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4540        .await
4541        .unwrap();
4542    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4543
4544    let buffer_b = project_b
4545        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4546        .await
4547        .unwrap();
4548
4549    let _handle = project_b.update(cx_b, |project, cx| {
4550        project.register_buffer_with_language_servers(&buffer_b, cx)
4551    });
4552    let fake_language_server = fake_language_servers.next().await.unwrap();
4553    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4554        Ok(Some(vec![
4555            lsp::TextEdit {
4556                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4557                new_text: "h".to_string(),
4558            },
4559            lsp::TextEdit {
4560                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4561                new_text: "y".to_string(),
4562            },
4563        ]))
4564    });
4565
4566    project_b
4567        .update(cx_b, |project, cx| {
4568            project.format(
4569                HashSet::from_iter([buffer_b.clone()]),
4570                LspFormatTarget::Buffers,
4571                true,
4572                FormatTrigger::Save,
4573                cx,
4574            )
4575        })
4576        .await
4577        .unwrap();
4578
4579    // The edits from the LSP are applied, and a final newline is added.
4580    assert_eq!(
4581        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4582        "let honey = \"two\"\n"
4583    );
4584
4585    // There is no `awk` command on Windows.
4586    #[cfg(not(target_os = "windows"))]
4587    {
4588        // Ensure buffer can be formatted using an external command. Notice how the
4589        // host's configuration is honored as opposed to using the guest's settings.
4590        cx_a.update(|cx| {
4591            SettingsStore::update_global(cx, |store, cx| {
4592                store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4593                    file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4594                        vec![Formatter::External {
4595                            command: "awk".into(),
4596                            arguments: Some(
4597                                vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
4598                            ),
4599                        }]
4600                        .into(),
4601                    )));
4602                });
4603            });
4604        });
4605
4606        executor.allow_parking();
4607        project_b
4608            .update(cx_b, |project, cx| {
4609                project.format(
4610                    HashSet::from_iter([buffer_b.clone()]),
4611                    LspFormatTarget::Buffers,
4612                    true,
4613                    FormatTrigger::Save,
4614                    cx,
4615                )
4616            })
4617            .await
4618            .unwrap();
4619        assert_eq!(
4620            buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4621            format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
4622        );
4623    }
4624}
4625
4626#[gpui::test(iterations = 10)]
4627async fn test_prettier_formatting_buffer(
4628    executor: BackgroundExecutor,
4629    cx_a: &mut TestAppContext,
4630    cx_b: &mut TestAppContext,
4631) {
4632    let mut server = TestServer::start(executor.clone()).await;
4633    let client_a = server.create_client(cx_a, "user_a").await;
4634    let client_b = server.create_client(cx_b, "user_b").await;
4635    server
4636        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4637        .await;
4638    let active_call_a = cx_a.read(ActiveCall::global);
4639
4640    let test_plugin = "test_plugin";
4641
4642    client_a.language_registry().add(Arc::new(Language::new(
4643        LanguageConfig {
4644            name: "TypeScript".into(),
4645            matcher: LanguageMatcher {
4646                path_suffixes: vec!["ts".to_string()],
4647                ..Default::default()
4648            },
4649            ..Default::default()
4650        },
4651        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
4652    )));
4653    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4654        "TypeScript",
4655        FakeLspAdapter {
4656            prettier_plugins: vec![test_plugin],
4657            ..Default::default()
4658        },
4659    );
4660
4661    // Here we insert a fake tree with a directory that exists on disk. This is needed
4662    // because later we'll invoke a command, which requires passing a working directory
4663    // that points to a valid location on disk.
4664    let directory = env::current_dir().unwrap();
4665    let buffer_text = "let one = \"two\"";
4666    client_a
4667        .fs()
4668        .insert_tree(&directory, json!({ "a.ts": buffer_text }))
4669        .await;
4670    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4671    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
4672    let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
4673    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
4674
4675    let project_id = active_call_a
4676        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4677        .await
4678        .unwrap();
4679    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4680    let (buffer_b, _) = project_b
4681        .update(cx_b, |p, cx| {
4682            p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
4683        })
4684        .await
4685        .unwrap();
4686
4687    cx_a.update(|cx| {
4688        SettingsStore::update_global(cx, |store, cx| {
4689            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4690                file.defaults.formatter = Some(SelectedFormatter::Auto);
4691                file.defaults.prettier = Some(PrettierSettings {
4692                    allowed: true,
4693                    ..PrettierSettings::default()
4694                });
4695            });
4696        });
4697    });
4698    cx_b.update(|cx| {
4699        SettingsStore::update_global(cx, |store, cx| {
4700            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4701                file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4702                    vec![Formatter::LanguageServer { name: None }].into(),
4703                )));
4704                file.defaults.prettier = Some(PrettierSettings {
4705                    allowed: true,
4706                    ..PrettierSettings::default()
4707                });
4708            });
4709        });
4710    });
4711    let fake_language_server = fake_language_servers.next().await.unwrap();
4712    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4713        panic!(
4714            "Unexpected: prettier should be preferred since it's enabled and language supports it"
4715        )
4716    });
4717
4718    project_b
4719        .update(cx_b, |project, cx| {
4720            project.format(
4721                HashSet::from_iter([buffer_b.clone()]),
4722                LspFormatTarget::Buffers,
4723                true,
4724                FormatTrigger::Save,
4725                cx,
4726            )
4727        })
4728        .await
4729        .unwrap();
4730
4731    executor.run_until_parked();
4732    assert_eq!(
4733        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4734        buffer_text.to_string() + "\n" + prettier_format_suffix,
4735        "Prettier formatting was not applied to client buffer after client's request"
4736    );
4737
4738    project_a
4739        .update(cx_a, |project, cx| {
4740            project.format(
4741                HashSet::from_iter([buffer_a.clone()]),
4742                LspFormatTarget::Buffers,
4743                true,
4744                FormatTrigger::Manual,
4745                cx,
4746            )
4747        })
4748        .await
4749        .unwrap();
4750
4751    executor.run_until_parked();
4752    assert_eq!(
4753        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4754        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
4755        "Prettier formatting was not applied to client buffer after host's request"
4756    );
4757}
4758
4759#[gpui::test(iterations = 10)]
4760async fn test_definition(
4761    executor: BackgroundExecutor,
4762    cx_a: &mut TestAppContext,
4763    cx_b: &mut TestAppContext,
4764) {
4765    let mut server = TestServer::start(executor.clone()).await;
4766    let client_a = server.create_client(cx_a, "user_a").await;
4767    let client_b = server.create_client(cx_b, "user_b").await;
4768    server
4769        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4770        .await;
4771    let active_call_a = cx_a.read(ActiveCall::global);
4772
4773    let mut fake_language_servers = client_a
4774        .language_registry()
4775        .register_fake_lsp("Rust", Default::default());
4776    client_a.language_registry().add(rust_lang());
4777
4778    client_a
4779        .fs()
4780        .insert_tree(
4781            path!("/root"),
4782            json!({
4783                "dir-1": {
4784                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
4785                },
4786                "dir-2": {
4787                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
4788                    "c.rs": "type T2 = usize;",
4789                }
4790            }),
4791        )
4792        .await;
4793    let (project_a, worktree_id) = client_a
4794        .build_local_project(path!("/root/dir-1"), cx_a)
4795        .await;
4796    let project_id = active_call_a
4797        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4798        .await
4799        .unwrap();
4800    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4801
4802    // Open the file on client B.
4803    let (buffer_b, _handle) = project_b
4804        .update(cx_b, |p, cx| {
4805            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
4806        })
4807        .await
4808        .unwrap();
4809
4810    // Request the definition of a symbol as the guest.
4811    let fake_language_server = fake_language_servers.next().await.unwrap();
4812    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4813        |_, _| async move {
4814            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4815                lsp::Location::new(
4816                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4817                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4818                ),
4819            )))
4820        },
4821    );
4822
4823    let definitions_1 = project_b
4824        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
4825        .await
4826        .unwrap();
4827    cx_b.read(|cx| {
4828        assert_eq!(definitions_1.len(), 1);
4829        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4830        let target_buffer = definitions_1[0].target.buffer.read(cx);
4831        assert_eq!(
4832            target_buffer.text(),
4833            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4834        );
4835        assert_eq!(
4836            definitions_1[0].target.range.to_point(target_buffer),
4837            Point::new(0, 6)..Point::new(0, 9)
4838        );
4839    });
4840
4841    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
4842    // the previous call to `definition`.
4843    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4844        |_, _| async move {
4845            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4846                lsp::Location::new(
4847                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4848                    lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
4849                ),
4850            )))
4851        },
4852    );
4853
4854    let definitions_2 = project_b
4855        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
4856        .await
4857        .unwrap();
4858    cx_b.read(|cx| {
4859        assert_eq!(definitions_2.len(), 1);
4860        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4861        let target_buffer = definitions_2[0].target.buffer.read(cx);
4862        assert_eq!(
4863            target_buffer.text(),
4864            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4865        );
4866        assert_eq!(
4867            definitions_2[0].target.range.to_point(target_buffer),
4868            Point::new(1, 6)..Point::new(1, 11)
4869        );
4870    });
4871    assert_eq!(
4872        definitions_1[0].target.buffer,
4873        definitions_2[0].target.buffer
4874    );
4875
4876    fake_language_server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>(
4877        |req, _| async move {
4878            assert_eq!(
4879                req.text_document_position_params.position,
4880                lsp::Position::new(0, 7)
4881            );
4882            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4883                lsp::Location::new(
4884                    lsp::Url::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
4885                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
4886                ),
4887            )))
4888        },
4889    );
4890
4891    let type_definitions = project_b
4892        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
4893        .await
4894        .unwrap();
4895    cx_b.read(|cx| {
4896        assert_eq!(type_definitions.len(), 1);
4897        let target_buffer = type_definitions[0].target.buffer.read(cx);
4898        assert_eq!(target_buffer.text(), "type T2 = usize;");
4899        assert_eq!(
4900            type_definitions[0].target.range.to_point(target_buffer),
4901            Point::new(0, 5)..Point::new(0, 7)
4902        );
4903    });
4904}
4905
4906#[gpui::test(iterations = 10)]
4907async fn test_references(
4908    executor: BackgroundExecutor,
4909    cx_a: &mut TestAppContext,
4910    cx_b: &mut TestAppContext,
4911) {
4912    let mut server = TestServer::start(executor.clone()).await;
4913    let client_a = server.create_client(cx_a, "user_a").await;
4914    let client_b = server.create_client(cx_b, "user_b").await;
4915    server
4916        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4917        .await;
4918    let active_call_a = cx_a.read(ActiveCall::global);
4919
4920    client_a.language_registry().add(rust_lang());
4921    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4922        "Rust",
4923        FakeLspAdapter {
4924            name: "my-fake-lsp-adapter",
4925            capabilities: lsp::ServerCapabilities {
4926                references_provider: Some(lsp::OneOf::Left(true)),
4927                ..Default::default()
4928            },
4929            ..Default::default()
4930        },
4931    );
4932
4933    client_a
4934        .fs()
4935        .insert_tree(
4936            path!("/root"),
4937            json!({
4938                "dir-1": {
4939                    "one.rs": "const ONE: usize = 1;",
4940                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
4941                },
4942                "dir-2": {
4943                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
4944                }
4945            }),
4946        )
4947        .await;
4948    let (project_a, worktree_id) = client_a
4949        .build_local_project(path!("/root/dir-1"), cx_a)
4950        .await;
4951    let project_id = active_call_a
4952        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4953        .await
4954        .unwrap();
4955    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4956
4957    // Open the file on client B.
4958    let (buffer_b, _handle) = project_b
4959        .update(cx_b, |p, cx| {
4960            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
4961        })
4962        .await
4963        .unwrap();
4964
4965    // Request references to a symbol as the guest.
4966    let fake_language_server = fake_language_servers.next().await.unwrap();
4967    let (lsp_response_tx, rx) = mpsc::unbounded::<Result<Option<Vec<lsp::Location>>>>();
4968    fake_language_server.set_request_handler::<lsp::request::References, _, _>({
4969        let rx = Arc::new(Mutex::new(Some(rx)));
4970        move |params, _| {
4971            assert_eq!(
4972                params.text_document_position.text_document.uri.as_str(),
4973                uri!("file:///root/dir-1/one.rs")
4974            );
4975            let rx = rx.clone();
4976            async move {
4977                let mut response_rx = rx.lock().take().unwrap();
4978                let result = response_rx.next().await.unwrap();
4979                *rx.lock() = Some(response_rx);
4980                result
4981            }
4982        }
4983    });
4984
4985    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
4986
4987    // User is informed that a request is pending.
4988    executor.run_until_parked();
4989    project_b.read_with(cx_b, |project, cx| {
4990        let status = project.language_server_statuses(cx).next().unwrap().1;
4991        assert_eq!(status.name, "my-fake-lsp-adapter");
4992        assert_eq!(
4993            status.pending_work.values().next().unwrap().message,
4994            Some("Finding references...".into())
4995        );
4996    });
4997
4998    // Cause the language server to respond.
4999    lsp_response_tx
5000        .unbounded_send(Ok(Some(vec![
5001            lsp::Location {
5002                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5003                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
5004            },
5005            lsp::Location {
5006                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
5007                range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
5008            },
5009            lsp::Location {
5010                uri: lsp::Url::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
5011                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
5012            },
5013        ])))
5014        .unwrap();
5015
5016    let references = references.await.unwrap();
5017    executor.run_until_parked();
5018    project_b.read_with(cx_b, |project, cx| {
5019        // User is informed that a request is no longer pending.
5020        let status = project.language_server_statuses(cx).next().unwrap().1;
5021        assert!(status.pending_work.is_empty());
5022
5023        assert_eq!(references.len(), 3);
5024        assert_eq!(project.worktrees(cx).count(), 2);
5025
5026        let two_buffer = references[0].buffer.read(cx);
5027        let three_buffer = references[2].buffer.read(cx);
5028        assert_eq!(
5029            two_buffer.file().unwrap().path().as_ref(),
5030            Path::new("two.rs")
5031        );
5032        assert_eq!(references[1].buffer, references[0].buffer);
5033        assert_eq!(
5034            three_buffer.file().unwrap().full_path(cx),
5035            Path::new(path!("/root/dir-2/three.rs"))
5036        );
5037
5038        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
5039        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
5040        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
5041    });
5042
5043    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5044
5045    // User is informed that a request is pending.
5046    executor.run_until_parked();
5047    project_b.read_with(cx_b, |project, cx| {
5048        let status = project.language_server_statuses(cx).next().unwrap().1;
5049        assert_eq!(status.name, "my-fake-lsp-adapter");
5050        assert_eq!(
5051            status.pending_work.values().next().unwrap().message,
5052            Some("Finding references...".into())
5053        );
5054    });
5055
5056    // Cause the LSP request to fail.
5057    lsp_response_tx
5058        .unbounded_send(Err(anyhow!("can't find references")))
5059        .unwrap();
5060    references.await.unwrap_err();
5061
5062    // User is informed that the request is no longer pending.
5063    executor.run_until_parked();
5064    project_b.read_with(cx_b, |project, cx| {
5065        let status = project.language_server_statuses(cx).next().unwrap().1;
5066        assert!(status.pending_work.is_empty());
5067    });
5068}
5069
5070#[gpui::test(iterations = 10)]
5071async fn test_project_search(
5072    executor: BackgroundExecutor,
5073    cx_a: &mut TestAppContext,
5074    cx_b: &mut TestAppContext,
5075) {
5076    let mut server = TestServer::start(executor.clone()).await;
5077    let client_a = server.create_client(cx_a, "user_a").await;
5078    let client_b = server.create_client(cx_b, "user_b").await;
5079    server
5080        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5081        .await;
5082    let active_call_a = cx_a.read(ActiveCall::global);
5083
5084    client_a
5085        .fs()
5086        .insert_tree(
5087            "/root",
5088            json!({
5089                "dir-1": {
5090                    "a": "hello world",
5091                    "b": "goodnight moon",
5092                    "c": "a world of goo",
5093                    "d": "world champion of clown world",
5094                },
5095                "dir-2": {
5096                    "e": "disney world is fun",
5097                }
5098            }),
5099        )
5100        .await;
5101    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
5102    let (worktree_2, _) = project_a
5103        .update(cx_a, |p, cx| {
5104            p.find_or_create_worktree("/root/dir-2", true, cx)
5105        })
5106        .await
5107        .unwrap();
5108    worktree_2
5109        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
5110        .await;
5111    let project_id = active_call_a
5112        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5113        .await
5114        .unwrap();
5115
5116    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5117
5118    // Perform a search as the guest.
5119    let mut results = HashMap::default();
5120    let search_rx = project_b.update(cx_b, |project, cx| {
5121        project.search(
5122            SearchQuery::text(
5123                "world",
5124                false,
5125                false,
5126                false,
5127                Default::default(),
5128                Default::default(),
5129                false,
5130                None,
5131            )
5132            .unwrap(),
5133            cx,
5134        )
5135    });
5136    while let Ok(result) = search_rx.recv().await {
5137        match result {
5138            SearchResult::Buffer { buffer, ranges } => {
5139                results.entry(buffer).or_insert(ranges);
5140            }
5141            SearchResult::LimitReached => {
5142                panic!(
5143                    "Unexpectedly reached search limit in tests. If you do want to assert limit-reached, change this panic call."
5144                )
5145            }
5146        };
5147    }
5148
5149    let mut ranges_by_path = results
5150        .into_iter()
5151        .map(|(buffer, ranges)| {
5152            buffer.read_with(cx_b, |buffer, cx| {
5153                let path = buffer.file().unwrap().full_path(cx);
5154                let offset_ranges = ranges
5155                    .into_iter()
5156                    .map(|range| range.to_offset(buffer))
5157                    .collect::<Vec<_>>();
5158                (path, offset_ranges)
5159            })
5160        })
5161        .collect::<Vec<_>>();
5162    ranges_by_path.sort_by_key(|(path, _)| path.clone());
5163
5164    assert_eq!(
5165        ranges_by_path,
5166        &[
5167            (PathBuf::from("dir-1/a"), vec![6..11]),
5168            (PathBuf::from("dir-1/c"), vec![2..7]),
5169            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
5170            (PathBuf::from("dir-2/e"), vec![7..12]),
5171        ]
5172    );
5173}
5174
5175#[gpui::test(iterations = 10)]
5176async fn test_document_highlights(
5177    executor: BackgroundExecutor,
5178    cx_a: &mut TestAppContext,
5179    cx_b: &mut TestAppContext,
5180) {
5181    let mut server = TestServer::start(executor.clone()).await;
5182    let client_a = server.create_client(cx_a, "user_a").await;
5183    let client_b = server.create_client(cx_b, "user_b").await;
5184    server
5185        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5186        .await;
5187    let active_call_a = cx_a.read(ActiveCall::global);
5188
5189    client_a
5190        .fs()
5191        .insert_tree(
5192            path!("/root-1"),
5193            json!({
5194                "main.rs": "fn double(number: i32) -> i32 { number + number }",
5195            }),
5196        )
5197        .await;
5198
5199    let mut fake_language_servers = client_a
5200        .language_registry()
5201        .register_fake_lsp("Rust", Default::default());
5202    client_a.language_registry().add(rust_lang());
5203
5204    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5205    let project_id = active_call_a
5206        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5207        .await
5208        .unwrap();
5209    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5210
5211    // Open the file on client B.
5212    let (buffer_b, _handle) = project_b
5213        .update(cx_b, |p, cx| {
5214            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5215        })
5216        .await
5217        .unwrap();
5218
5219    // Request document highlights as the guest.
5220    let fake_language_server = fake_language_servers.next().await.unwrap();
5221    fake_language_server.set_request_handler::<lsp::request::DocumentHighlightRequest, _, _>(
5222        |params, _| async move {
5223            assert_eq!(
5224                params
5225                    .text_document_position_params
5226                    .text_document
5227                    .uri
5228                    .as_str(),
5229                uri!("file:///root-1/main.rs")
5230            );
5231            assert_eq!(
5232                params.text_document_position_params.position,
5233                lsp::Position::new(0, 34)
5234            );
5235            Ok(Some(vec![
5236                lsp::DocumentHighlight {
5237                    kind: Some(lsp::DocumentHighlightKind::WRITE),
5238                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
5239                },
5240                lsp::DocumentHighlight {
5241                    kind: Some(lsp::DocumentHighlightKind::READ),
5242                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
5243                },
5244                lsp::DocumentHighlight {
5245                    kind: Some(lsp::DocumentHighlightKind::READ),
5246                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
5247                },
5248            ]))
5249        },
5250    );
5251
5252    let highlights = project_b
5253        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
5254        .await
5255        .unwrap();
5256
5257    buffer_b.read_with(cx_b, |buffer, _| {
5258        let snapshot = buffer.snapshot();
5259
5260        let highlights = highlights
5261            .into_iter()
5262            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
5263            .collect::<Vec<_>>();
5264        assert_eq!(
5265            highlights,
5266            &[
5267                (lsp::DocumentHighlightKind::WRITE, 10..16),
5268                (lsp::DocumentHighlightKind::READ, 32..38),
5269                (lsp::DocumentHighlightKind::READ, 41..47)
5270            ]
5271        )
5272    });
5273}
5274
5275#[gpui::test(iterations = 10)]
5276async fn test_lsp_hover(
5277    executor: BackgroundExecutor,
5278    cx_a: &mut TestAppContext,
5279    cx_b: &mut TestAppContext,
5280) {
5281    let mut server = TestServer::start(executor.clone()).await;
5282    let client_a = server.create_client(cx_a, "user_a").await;
5283    let client_b = server.create_client(cx_b, "user_b").await;
5284    server
5285        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5286        .await;
5287    let active_call_a = cx_a.read(ActiveCall::global);
5288
5289    client_a
5290        .fs()
5291        .insert_tree(
5292            path!("/root-1"),
5293            json!({
5294                "main.rs": "use std::collections::HashMap;",
5295            }),
5296        )
5297        .await;
5298
5299    client_a.language_registry().add(rust_lang());
5300    let language_server_names = ["rust-analyzer", "CrabLang-ls"];
5301    let mut language_servers = [
5302        client_a.language_registry().register_fake_lsp(
5303            "Rust",
5304            FakeLspAdapter {
5305                name: "rust-analyzer",
5306                capabilities: lsp::ServerCapabilities {
5307                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5308                    ..lsp::ServerCapabilities::default()
5309                },
5310                ..FakeLspAdapter::default()
5311            },
5312        ),
5313        client_a.language_registry().register_fake_lsp(
5314            "Rust",
5315            FakeLspAdapter {
5316                name: "CrabLang-ls",
5317                capabilities: lsp::ServerCapabilities {
5318                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5319                    ..lsp::ServerCapabilities::default()
5320                },
5321                ..FakeLspAdapter::default()
5322            },
5323        ),
5324    ];
5325
5326    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5327    let project_id = active_call_a
5328        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5329        .await
5330        .unwrap();
5331    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5332
5333    // Open the file as the guest
5334    let (buffer_b, _handle) = project_b
5335        .update(cx_b, |p, cx| {
5336            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5337        })
5338        .await
5339        .unwrap();
5340
5341    let mut servers_with_hover_requests = HashMap::default();
5342    for i in 0..language_server_names.len() {
5343        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
5344            panic!(
5345                "Failed to get language server #{i} with name {}",
5346                &language_server_names[i]
5347            )
5348        });
5349        let new_server_name = new_server.server.name();
5350        assert!(
5351            !servers_with_hover_requests.contains_key(&new_server_name),
5352            "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
5353        );
5354        match new_server_name.as_ref() {
5355            "CrabLang-ls" => {
5356                servers_with_hover_requests.insert(
5357                    new_server_name.clone(),
5358                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5359                        move |params, _| {
5360                            assert_eq!(
5361                                params
5362                                    .text_document_position_params
5363                                    .text_document
5364                                    .uri
5365                                    .as_str(),
5366                                uri!("file:///root-1/main.rs")
5367                            );
5368                            let name = new_server_name.clone();
5369                            async move {
5370                                Ok(Some(lsp::Hover {
5371                                    contents: lsp::HoverContents::Scalar(
5372                                        lsp::MarkedString::String(format!("{name} hover")),
5373                                    ),
5374                                    range: None,
5375                                }))
5376                            }
5377                        },
5378                    ),
5379                );
5380            }
5381            "rust-analyzer" => {
5382                servers_with_hover_requests.insert(
5383                    new_server_name.clone(),
5384                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5385                        |params, _| async move {
5386                            assert_eq!(
5387                                params
5388                                    .text_document_position_params
5389                                    .text_document
5390                                    .uri
5391                                    .as_str(),
5392                                uri!("file:///root-1/main.rs")
5393                            );
5394                            assert_eq!(
5395                                params.text_document_position_params.position,
5396                                lsp::Position::new(0, 22)
5397                            );
5398                            Ok(Some(lsp::Hover {
5399                                contents: lsp::HoverContents::Array(vec![
5400                                    lsp::MarkedString::String("Test hover content.".to_string()),
5401                                    lsp::MarkedString::LanguageString(lsp::LanguageString {
5402                                        language: "Rust".to_string(),
5403                                        value: "let foo = 42;".to_string(),
5404                                    }),
5405                                ]),
5406                                range: Some(lsp::Range::new(
5407                                    lsp::Position::new(0, 22),
5408                                    lsp::Position::new(0, 29),
5409                                )),
5410                            }))
5411                        },
5412                    ),
5413                );
5414            }
5415            unexpected => panic!("Unexpected server name: {unexpected}"),
5416        }
5417    }
5418
5419    // Request hover information as the guest.
5420    let mut hovers = project_b
5421        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
5422        .await;
5423    assert_eq!(
5424        hovers.len(),
5425        2,
5426        "Expected two hovers from both language servers, but got: {hovers:?}"
5427    );
5428
5429    let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
5430        |mut hover_request| async move {
5431            hover_request
5432                .next()
5433                .await
5434                .expect("All hover requests should have been triggered")
5435        },
5436    ))
5437    .await;
5438
5439    hovers.sort_by_key(|hover| hover.contents.len());
5440    let first_hover = hovers.first().cloned().unwrap();
5441    assert_eq!(
5442        first_hover.contents,
5443        vec![project::HoverBlock {
5444            text: "CrabLang-ls hover".to_string(),
5445            kind: HoverBlockKind::Markdown,
5446        },]
5447    );
5448    let second_hover = hovers.last().cloned().unwrap();
5449    assert_eq!(
5450        second_hover.contents,
5451        vec![
5452            project::HoverBlock {
5453                text: "Test hover content.".to_string(),
5454                kind: HoverBlockKind::Markdown,
5455            },
5456            project::HoverBlock {
5457                text: "let foo = 42;".to_string(),
5458                kind: HoverBlockKind::Code {
5459                    language: "Rust".to_string()
5460                },
5461            }
5462        ]
5463    );
5464    buffer_b.read_with(cx_b, |buffer, _| {
5465        let snapshot = buffer.snapshot();
5466        assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
5467    });
5468}
5469
5470#[gpui::test(iterations = 10)]
5471async fn test_project_symbols(
5472    executor: BackgroundExecutor,
5473    cx_a: &mut TestAppContext,
5474    cx_b: &mut TestAppContext,
5475) {
5476    let mut server = TestServer::start(executor.clone()).await;
5477    let client_a = server.create_client(cx_a, "user_a").await;
5478    let client_b = server.create_client(cx_b, "user_b").await;
5479    server
5480        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5481        .await;
5482    let active_call_a = cx_a.read(ActiveCall::global);
5483
5484    client_a.language_registry().add(rust_lang());
5485    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5486        "Rust",
5487        FakeLspAdapter {
5488            capabilities: lsp::ServerCapabilities {
5489                workspace_symbol_provider: Some(OneOf::Left(true)),
5490                ..Default::default()
5491            },
5492            ..Default::default()
5493        },
5494    );
5495
5496    client_a
5497        .fs()
5498        .insert_tree(
5499            path!("/code"),
5500            json!({
5501                "crate-1": {
5502                    "one.rs": "const ONE: usize = 1;",
5503                },
5504                "crate-2": {
5505                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
5506                },
5507                "private": {
5508                    "passwords.txt": "the-password",
5509                }
5510            }),
5511        )
5512        .await;
5513    let (project_a, worktree_id) = client_a
5514        .build_local_project(path!("/code/crate-1"), cx_a)
5515        .await;
5516    let project_id = active_call_a
5517        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5518        .await
5519        .unwrap();
5520    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5521
5522    // Cause the language server to start.
5523    let _buffer = project_b
5524        .update(cx_b, |p, cx| {
5525            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
5526        })
5527        .await
5528        .unwrap();
5529
5530    let fake_language_server = fake_language_servers.next().await.unwrap();
5531    fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
5532        |_, _| async move {
5533            Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
5534                #[allow(deprecated)]
5535                lsp::SymbolInformation {
5536                    name: "TWO".into(),
5537                    location: lsp::Location {
5538                        uri: lsp::Url::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
5539                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5540                    },
5541                    kind: lsp::SymbolKind::CONSTANT,
5542                    tags: None,
5543                    container_name: None,
5544                    deprecated: None,
5545                },
5546            ])))
5547        },
5548    );
5549
5550    // Request the definition of a symbol as the guest.
5551    let symbols = project_b
5552        .update(cx_b, |p, cx| p.symbols("two", cx))
5553        .await
5554        .unwrap();
5555    assert_eq!(symbols.len(), 1);
5556    assert_eq!(symbols[0].name, "TWO");
5557
5558    // Open one of the returned symbols.
5559    let buffer_b_2 = project_b
5560        .update(cx_b, |project, cx| {
5561            project.open_buffer_for_symbol(&symbols[0], cx)
5562        })
5563        .await
5564        .unwrap();
5565
5566    buffer_b_2.read_with(cx_b, |buffer, cx| {
5567        assert_eq!(
5568            buffer.file().unwrap().full_path(cx),
5569            Path::new(path!("/code/crate-2/two.rs"))
5570        );
5571    });
5572
5573    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
5574    let mut fake_symbol = symbols[0].clone();
5575    fake_symbol.path.path = Path::new(path!("/code/secrets")).into();
5576    let error = project_b
5577        .update(cx_b, |project, cx| {
5578            project.open_buffer_for_symbol(&fake_symbol, cx)
5579        })
5580        .await
5581        .unwrap_err();
5582    assert!(error.to_string().contains("invalid symbol signature"));
5583}
5584
5585#[gpui::test(iterations = 10)]
5586async fn test_open_buffer_while_getting_definition_pointing_to_it(
5587    executor: BackgroundExecutor,
5588    cx_a: &mut TestAppContext,
5589    cx_b: &mut TestAppContext,
5590    mut rng: StdRng,
5591) {
5592    let mut server = TestServer::start(executor.clone()).await;
5593    let client_a = server.create_client(cx_a, "user_a").await;
5594    let client_b = server.create_client(cx_b, "user_b").await;
5595    server
5596        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5597        .await;
5598    let active_call_a = cx_a.read(ActiveCall::global);
5599
5600    client_a.language_registry().add(rust_lang());
5601    let mut fake_language_servers = client_a
5602        .language_registry()
5603        .register_fake_lsp("Rust", Default::default());
5604
5605    client_a
5606        .fs()
5607        .insert_tree(
5608            path!("/root"),
5609            json!({
5610                "a.rs": "const ONE: usize = b::TWO;",
5611                "b.rs": "const TWO: usize = 2",
5612            }),
5613        )
5614        .await;
5615    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
5616    let project_id = active_call_a
5617        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5618        .await
5619        .unwrap();
5620    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5621
5622    let (buffer_b1, _lsp) = project_b
5623        .update(cx_b, |p, cx| {
5624            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
5625        })
5626        .await
5627        .unwrap();
5628
5629    let fake_language_server = fake_language_servers.next().await.unwrap();
5630    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
5631        |_, _| async move {
5632            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5633                lsp::Location::new(
5634                    lsp::Url::from_file_path(path!("/root/b.rs")).unwrap(),
5635                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5636                ),
5637            )))
5638        },
5639    );
5640
5641    let definitions;
5642    let buffer_b2;
5643    if rng.r#gen() {
5644        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5645        (buffer_b2, _) = project_b
5646            .update(cx_b, |p, cx| {
5647                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5648            })
5649            .await
5650            .unwrap();
5651    } else {
5652        (buffer_b2, _) = project_b
5653            .update(cx_b, |p, cx| {
5654                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5655            })
5656            .await
5657            .unwrap();
5658        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5659    }
5660
5661    let definitions = definitions.await.unwrap();
5662    assert_eq!(definitions.len(), 1);
5663    assert_eq!(definitions[0].target.buffer, buffer_b2);
5664}
5665
5666#[gpui::test(iterations = 10)]
5667async fn test_contacts(
5668    executor: BackgroundExecutor,
5669    cx_a: &mut TestAppContext,
5670    cx_b: &mut TestAppContext,
5671    cx_c: &mut TestAppContext,
5672    cx_d: &mut TestAppContext,
5673) {
5674    let mut server = TestServer::start(executor.clone()).await;
5675    let client_a = server.create_client(cx_a, "user_a").await;
5676    let client_b = server.create_client(cx_b, "user_b").await;
5677    let client_c = server.create_client(cx_c, "user_c").await;
5678    let client_d = server.create_client(cx_d, "user_d").await;
5679    server
5680        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
5681        .await;
5682    let active_call_a = cx_a.read(ActiveCall::global);
5683    let active_call_b = cx_b.read(ActiveCall::global);
5684    let active_call_c = cx_c.read(ActiveCall::global);
5685    let _active_call_d = cx_d.read(ActiveCall::global);
5686
5687    executor.run_until_parked();
5688    assert_eq!(
5689        contacts(&client_a, cx_a),
5690        [
5691            ("user_b".to_string(), "online", "free"),
5692            ("user_c".to_string(), "online", "free")
5693        ]
5694    );
5695    assert_eq!(
5696        contacts(&client_b, cx_b),
5697        [
5698            ("user_a".to_string(), "online", "free"),
5699            ("user_c".to_string(), "online", "free")
5700        ]
5701    );
5702    assert_eq!(
5703        contacts(&client_c, cx_c),
5704        [
5705            ("user_a".to_string(), "online", "free"),
5706            ("user_b".to_string(), "online", "free")
5707        ]
5708    );
5709    assert_eq!(contacts(&client_d, cx_d), []);
5710
5711    server.disconnect_client(client_c.peer_id().unwrap());
5712    server.forbid_connections();
5713    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5714    assert_eq!(
5715        contacts(&client_a, cx_a),
5716        [
5717            ("user_b".to_string(), "online", "free"),
5718            ("user_c".to_string(), "offline", "free")
5719        ]
5720    );
5721    assert_eq!(
5722        contacts(&client_b, cx_b),
5723        [
5724            ("user_a".to_string(), "online", "free"),
5725            ("user_c".to_string(), "offline", "free")
5726        ]
5727    );
5728    assert_eq!(contacts(&client_c, cx_c), []);
5729    assert_eq!(contacts(&client_d, cx_d), []);
5730
5731    server.allow_connections();
5732    client_c
5733        .authenticate_and_connect(false, &cx_c.to_async())
5734        .await
5735        .into_response()
5736        .unwrap();
5737
5738    executor.run_until_parked();
5739    assert_eq!(
5740        contacts(&client_a, cx_a),
5741        [
5742            ("user_b".to_string(), "online", "free"),
5743            ("user_c".to_string(), "online", "free")
5744        ]
5745    );
5746    assert_eq!(
5747        contacts(&client_b, cx_b),
5748        [
5749            ("user_a".to_string(), "online", "free"),
5750            ("user_c".to_string(), "online", "free")
5751        ]
5752    );
5753    assert_eq!(
5754        contacts(&client_c, cx_c),
5755        [
5756            ("user_a".to_string(), "online", "free"),
5757            ("user_b".to_string(), "online", "free")
5758        ]
5759    );
5760    assert_eq!(contacts(&client_d, cx_d), []);
5761
5762    active_call_a
5763        .update(cx_a, |call, cx| {
5764            call.invite(client_b.user_id().unwrap(), None, cx)
5765        })
5766        .await
5767        .unwrap();
5768    executor.run_until_parked();
5769    assert_eq!(
5770        contacts(&client_a, cx_a),
5771        [
5772            ("user_b".to_string(), "online", "busy"),
5773            ("user_c".to_string(), "online", "free")
5774        ]
5775    );
5776    assert_eq!(
5777        contacts(&client_b, cx_b),
5778        [
5779            ("user_a".to_string(), "online", "busy"),
5780            ("user_c".to_string(), "online", "free")
5781        ]
5782    );
5783    assert_eq!(
5784        contacts(&client_c, cx_c),
5785        [
5786            ("user_a".to_string(), "online", "busy"),
5787            ("user_b".to_string(), "online", "busy")
5788        ]
5789    );
5790    assert_eq!(contacts(&client_d, cx_d), []);
5791
5792    // Client B and client D become contacts while client B is being called.
5793    server
5794        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
5795        .await;
5796    executor.run_until_parked();
5797    assert_eq!(
5798        contacts(&client_a, cx_a),
5799        [
5800            ("user_b".to_string(), "online", "busy"),
5801            ("user_c".to_string(), "online", "free")
5802        ]
5803    );
5804    assert_eq!(
5805        contacts(&client_b, cx_b),
5806        [
5807            ("user_a".to_string(), "online", "busy"),
5808            ("user_c".to_string(), "online", "free"),
5809            ("user_d".to_string(), "online", "free"),
5810        ]
5811    );
5812    assert_eq!(
5813        contacts(&client_c, cx_c),
5814        [
5815            ("user_a".to_string(), "online", "busy"),
5816            ("user_b".to_string(), "online", "busy")
5817        ]
5818    );
5819    assert_eq!(
5820        contacts(&client_d, cx_d),
5821        [("user_b".to_string(), "online", "busy")]
5822    );
5823
5824    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
5825    executor.run_until_parked();
5826    assert_eq!(
5827        contacts(&client_a, cx_a),
5828        [
5829            ("user_b".to_string(), "online", "free"),
5830            ("user_c".to_string(), "online", "free")
5831        ]
5832    );
5833    assert_eq!(
5834        contacts(&client_b, cx_b),
5835        [
5836            ("user_a".to_string(), "online", "free"),
5837            ("user_c".to_string(), "online", "free"),
5838            ("user_d".to_string(), "online", "free")
5839        ]
5840    );
5841    assert_eq!(
5842        contacts(&client_c, cx_c),
5843        [
5844            ("user_a".to_string(), "online", "free"),
5845            ("user_b".to_string(), "online", "free")
5846        ]
5847    );
5848    assert_eq!(
5849        contacts(&client_d, cx_d),
5850        [("user_b".to_string(), "online", "free")]
5851    );
5852
5853    active_call_c
5854        .update(cx_c, |call, cx| {
5855            call.invite(client_a.user_id().unwrap(), None, cx)
5856        })
5857        .await
5858        .unwrap();
5859    executor.run_until_parked();
5860    assert_eq!(
5861        contacts(&client_a, cx_a),
5862        [
5863            ("user_b".to_string(), "online", "free"),
5864            ("user_c".to_string(), "online", "busy")
5865        ]
5866    );
5867    assert_eq!(
5868        contacts(&client_b, cx_b),
5869        [
5870            ("user_a".to_string(), "online", "busy"),
5871            ("user_c".to_string(), "online", "busy"),
5872            ("user_d".to_string(), "online", "free")
5873        ]
5874    );
5875    assert_eq!(
5876        contacts(&client_c, cx_c),
5877        [
5878            ("user_a".to_string(), "online", "busy"),
5879            ("user_b".to_string(), "online", "free")
5880        ]
5881    );
5882    assert_eq!(
5883        contacts(&client_d, cx_d),
5884        [("user_b".to_string(), "online", "free")]
5885    );
5886
5887    active_call_a
5888        .update(cx_a, |call, cx| call.accept_incoming(cx))
5889        .await
5890        .unwrap();
5891    executor.run_until_parked();
5892    assert_eq!(
5893        contacts(&client_a, cx_a),
5894        [
5895            ("user_b".to_string(), "online", "free"),
5896            ("user_c".to_string(), "online", "busy")
5897        ]
5898    );
5899    assert_eq!(
5900        contacts(&client_b, cx_b),
5901        [
5902            ("user_a".to_string(), "online", "busy"),
5903            ("user_c".to_string(), "online", "busy"),
5904            ("user_d".to_string(), "online", "free")
5905        ]
5906    );
5907    assert_eq!(
5908        contacts(&client_c, cx_c),
5909        [
5910            ("user_a".to_string(), "online", "busy"),
5911            ("user_b".to_string(), "online", "free")
5912        ]
5913    );
5914    assert_eq!(
5915        contacts(&client_d, cx_d),
5916        [("user_b".to_string(), "online", "free")]
5917    );
5918
5919    active_call_a
5920        .update(cx_a, |call, cx| {
5921            call.invite(client_b.user_id().unwrap(), None, cx)
5922        })
5923        .await
5924        .unwrap();
5925    executor.run_until_parked();
5926    assert_eq!(
5927        contacts(&client_a, cx_a),
5928        [
5929            ("user_b".to_string(), "online", "busy"),
5930            ("user_c".to_string(), "online", "busy")
5931        ]
5932    );
5933    assert_eq!(
5934        contacts(&client_b, cx_b),
5935        [
5936            ("user_a".to_string(), "online", "busy"),
5937            ("user_c".to_string(), "online", "busy"),
5938            ("user_d".to_string(), "online", "free")
5939        ]
5940    );
5941    assert_eq!(
5942        contacts(&client_c, cx_c),
5943        [
5944            ("user_a".to_string(), "online", "busy"),
5945            ("user_b".to_string(), "online", "busy")
5946        ]
5947    );
5948    assert_eq!(
5949        contacts(&client_d, cx_d),
5950        [("user_b".to_string(), "online", "busy")]
5951    );
5952
5953    active_call_a
5954        .update(cx_a, |call, cx| call.hang_up(cx))
5955        .await
5956        .unwrap();
5957    executor.run_until_parked();
5958    assert_eq!(
5959        contacts(&client_a, cx_a),
5960        [
5961            ("user_b".to_string(), "online", "free"),
5962            ("user_c".to_string(), "online", "free")
5963        ]
5964    );
5965    assert_eq!(
5966        contacts(&client_b, cx_b),
5967        [
5968            ("user_a".to_string(), "online", "free"),
5969            ("user_c".to_string(), "online", "free"),
5970            ("user_d".to_string(), "online", "free")
5971        ]
5972    );
5973    assert_eq!(
5974        contacts(&client_c, cx_c),
5975        [
5976            ("user_a".to_string(), "online", "free"),
5977            ("user_b".to_string(), "online", "free")
5978        ]
5979    );
5980    assert_eq!(
5981        contacts(&client_d, cx_d),
5982        [("user_b".to_string(), "online", "free")]
5983    );
5984
5985    active_call_a
5986        .update(cx_a, |call, cx| {
5987            call.invite(client_b.user_id().unwrap(), None, cx)
5988        })
5989        .await
5990        .unwrap();
5991    executor.run_until_parked();
5992    assert_eq!(
5993        contacts(&client_a, cx_a),
5994        [
5995            ("user_b".to_string(), "online", "busy"),
5996            ("user_c".to_string(), "online", "free")
5997        ]
5998    );
5999    assert_eq!(
6000        contacts(&client_b, cx_b),
6001        [
6002            ("user_a".to_string(), "online", "busy"),
6003            ("user_c".to_string(), "online", "free"),
6004            ("user_d".to_string(), "online", "free")
6005        ]
6006    );
6007    assert_eq!(
6008        contacts(&client_c, cx_c),
6009        [
6010            ("user_a".to_string(), "online", "busy"),
6011            ("user_b".to_string(), "online", "busy")
6012        ]
6013    );
6014    assert_eq!(
6015        contacts(&client_d, cx_d),
6016        [("user_b".to_string(), "online", "busy")]
6017    );
6018
6019    server.forbid_connections();
6020    server.disconnect_client(client_a.peer_id().unwrap());
6021    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6022    assert_eq!(contacts(&client_a, cx_a), []);
6023    assert_eq!(
6024        contacts(&client_b, cx_b),
6025        [
6026            ("user_a".to_string(), "offline", "free"),
6027            ("user_c".to_string(), "online", "free"),
6028            ("user_d".to_string(), "online", "free")
6029        ]
6030    );
6031    assert_eq!(
6032        contacts(&client_c, cx_c),
6033        [
6034            ("user_a".to_string(), "offline", "free"),
6035            ("user_b".to_string(), "online", "free")
6036        ]
6037    );
6038    assert_eq!(
6039        contacts(&client_d, cx_d),
6040        [("user_b".to_string(), "online", "free")]
6041    );
6042
6043    // Test removing a contact
6044    client_b
6045        .user_store()
6046        .update(cx_b, |store, cx| {
6047            store.remove_contact(client_c.user_id().unwrap(), cx)
6048        })
6049        .await
6050        .unwrap();
6051    executor.run_until_parked();
6052    assert_eq!(
6053        contacts(&client_b, cx_b),
6054        [
6055            ("user_a".to_string(), "offline", "free"),
6056            ("user_d".to_string(), "online", "free")
6057        ]
6058    );
6059    assert_eq!(
6060        contacts(&client_c, cx_c),
6061        [("user_a".to_string(), "offline", "free"),]
6062    );
6063
6064    fn contacts(
6065        client: &TestClient,
6066        cx: &TestAppContext,
6067    ) -> Vec<(String, &'static str, &'static str)> {
6068        client.user_store().read_with(cx, |store, _| {
6069            store
6070                .contacts()
6071                .iter()
6072                .map(|contact| {
6073                    (
6074                        contact.user.github_login.clone(),
6075                        if contact.online { "online" } else { "offline" },
6076                        if contact.busy { "busy" } else { "free" },
6077                    )
6078                })
6079                .collect()
6080        })
6081    }
6082}
6083
6084#[gpui::test(iterations = 10)]
6085async fn test_contact_requests(
6086    executor: BackgroundExecutor,
6087    cx_a: &mut TestAppContext,
6088    cx_a2: &mut TestAppContext,
6089    cx_b: &mut TestAppContext,
6090    cx_b2: &mut TestAppContext,
6091    cx_c: &mut TestAppContext,
6092    cx_c2: &mut TestAppContext,
6093) {
6094    // Connect to a server as 3 clients.
6095    let mut server = TestServer::start(executor.clone()).await;
6096    let client_a = server.create_client(cx_a, "user_a").await;
6097    let client_a2 = server.create_client(cx_a2, "user_a").await;
6098    let client_b = server.create_client(cx_b, "user_b").await;
6099    let client_b2 = server.create_client(cx_b2, "user_b").await;
6100    let client_c = server.create_client(cx_c, "user_c").await;
6101    let client_c2 = server.create_client(cx_c2, "user_c").await;
6102
6103    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
6104    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
6105    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
6106
6107    // User A and User C request that user B become their contact.
6108    client_a
6109        .user_store()
6110        .update(cx_a, |store, cx| {
6111            store.request_contact(client_b.user_id().unwrap(), cx)
6112        })
6113        .await
6114        .unwrap();
6115    client_c
6116        .user_store()
6117        .update(cx_c, |store, cx| {
6118            store.request_contact(client_b.user_id().unwrap(), cx)
6119        })
6120        .await
6121        .unwrap();
6122    executor.run_until_parked();
6123
6124    // All users see the pending request appear in all their clients.
6125    assert_eq!(
6126        client_a.summarize_contacts(cx_a).outgoing_requests,
6127        &["user_b"]
6128    );
6129    assert_eq!(
6130        client_a2.summarize_contacts(cx_a2).outgoing_requests,
6131        &["user_b"]
6132    );
6133    assert_eq!(
6134        client_b.summarize_contacts(cx_b).incoming_requests,
6135        &["user_a", "user_c"]
6136    );
6137    assert_eq!(
6138        client_b2.summarize_contacts(cx_b2).incoming_requests,
6139        &["user_a", "user_c"]
6140    );
6141    assert_eq!(
6142        client_c.summarize_contacts(cx_c).outgoing_requests,
6143        &["user_b"]
6144    );
6145    assert_eq!(
6146        client_c2.summarize_contacts(cx_c2).outgoing_requests,
6147        &["user_b"]
6148    );
6149
6150    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
6151    disconnect_and_reconnect(&client_a, cx_a).await;
6152    disconnect_and_reconnect(&client_b, cx_b).await;
6153    disconnect_and_reconnect(&client_c, cx_c).await;
6154    executor.run_until_parked();
6155    assert_eq!(
6156        client_a.summarize_contacts(cx_a).outgoing_requests,
6157        &["user_b"]
6158    );
6159    assert_eq!(
6160        client_b.summarize_contacts(cx_b).incoming_requests,
6161        &["user_a", "user_c"]
6162    );
6163    assert_eq!(
6164        client_c.summarize_contacts(cx_c).outgoing_requests,
6165        &["user_b"]
6166    );
6167
6168    // User B accepts the request from user A.
6169    client_b
6170        .user_store()
6171        .update(cx_b, |store, cx| {
6172            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6173        })
6174        .await
6175        .unwrap();
6176
6177    executor.run_until_parked();
6178
6179    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
6180    let contacts_b = client_b.summarize_contacts(cx_b);
6181    assert_eq!(contacts_b.current, &["user_a"]);
6182    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
6183    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6184    assert_eq!(contacts_b2.current, &["user_a"]);
6185    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
6186
6187    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
6188    let contacts_a = client_a.summarize_contacts(cx_a);
6189    assert_eq!(contacts_a.current, &["user_b"]);
6190    assert!(contacts_a.outgoing_requests.is_empty());
6191    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
6192    assert_eq!(contacts_a2.current, &["user_b"]);
6193    assert!(contacts_a2.outgoing_requests.is_empty());
6194
6195    // Contacts are present upon connecting (tested here via disconnect/reconnect)
6196    disconnect_and_reconnect(&client_a, cx_a).await;
6197    disconnect_and_reconnect(&client_b, cx_b).await;
6198    disconnect_and_reconnect(&client_c, cx_c).await;
6199    executor.run_until_parked();
6200    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6201    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6202    assert_eq!(
6203        client_b.summarize_contacts(cx_b).incoming_requests,
6204        &["user_c"]
6205    );
6206    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6207    assert_eq!(
6208        client_c.summarize_contacts(cx_c).outgoing_requests,
6209        &["user_b"]
6210    );
6211
6212    // User B rejects the request from user C.
6213    client_b
6214        .user_store()
6215        .update(cx_b, |store, cx| {
6216            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
6217        })
6218        .await
6219        .unwrap();
6220
6221    executor.run_until_parked();
6222
6223    // User B doesn't see user C as their contact, and the incoming request from them is removed.
6224    let contacts_b = client_b.summarize_contacts(cx_b);
6225    assert_eq!(contacts_b.current, &["user_a"]);
6226    assert!(contacts_b.incoming_requests.is_empty());
6227    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6228    assert_eq!(contacts_b2.current, &["user_a"]);
6229    assert!(contacts_b2.incoming_requests.is_empty());
6230
6231    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
6232    let contacts_c = client_c.summarize_contacts(cx_c);
6233    assert!(contacts_c.current.is_empty());
6234    assert!(contacts_c.outgoing_requests.is_empty());
6235    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
6236    assert!(contacts_c2.current.is_empty());
6237    assert!(contacts_c2.outgoing_requests.is_empty());
6238
6239    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
6240    disconnect_and_reconnect(&client_a, cx_a).await;
6241    disconnect_and_reconnect(&client_b, cx_b).await;
6242    disconnect_and_reconnect(&client_c, cx_c).await;
6243    executor.run_until_parked();
6244    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6245    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6246    assert!(
6247        client_b
6248            .summarize_contacts(cx_b)
6249            .incoming_requests
6250            .is_empty()
6251    );
6252    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6253    assert!(
6254        client_c
6255            .summarize_contacts(cx_c)
6256            .outgoing_requests
6257            .is_empty()
6258    );
6259
6260    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
6261        client.disconnect(&cx.to_async());
6262        client.clear_contacts(cx).await;
6263        client
6264            .authenticate_and_connect(false, &cx.to_async())
6265            .await
6266            .into_response()
6267            .unwrap();
6268    }
6269}
6270
6271#[gpui::test(iterations = 10)]
6272async fn test_join_call_after_screen_was_shared(
6273    executor: BackgroundExecutor,
6274    cx_a: &mut TestAppContext,
6275    cx_b: &mut TestAppContext,
6276) {
6277    let mut server = TestServer::start(executor.clone()).await;
6278
6279    let client_a = server.create_client(cx_a, "user_a").await;
6280    let client_b = server.create_client(cx_b, "user_b").await;
6281    server
6282        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6283        .await;
6284
6285    let active_call_a = cx_a.read(ActiveCall::global);
6286    let active_call_b = cx_b.read(ActiveCall::global);
6287
6288    // Call users B and C from client A.
6289    active_call_a
6290        .update(cx_a, |call, cx| {
6291            call.invite(client_b.user_id().unwrap(), None, cx)
6292        })
6293        .await
6294        .unwrap();
6295
6296    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6297    executor.run_until_parked();
6298    assert_eq!(
6299        room_participants(&room_a, cx_a),
6300        RoomParticipants {
6301            remote: Default::default(),
6302            pending: vec!["user_b".to_string()]
6303        }
6304    );
6305
6306    // User B receives the call.
6307
6308    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6309    let call_b = incoming_call_b.next().await.unwrap().unwrap();
6310    assert_eq!(call_b.calling_user.github_login, "user_a");
6311
6312    // User A shares their screen
6313    let display = gpui::TestScreenCaptureSource::new();
6314    cx_a.set_screen_capture_sources(vec![display]);
6315    active_call_a
6316        .update(cx_a, |call, cx| {
6317            call.room()
6318                .unwrap()
6319                .update(cx, |room, cx| room.share_screen(cx))
6320        })
6321        .await
6322        .unwrap();
6323
6324    client_b.user_store().update(cx_b, |user_store, _| {
6325        user_store.clear_cache();
6326    });
6327
6328    // User B joins the room
6329    active_call_b
6330        .update(cx_b, |call, cx| call.accept_incoming(cx))
6331        .await
6332        .unwrap();
6333
6334    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6335    assert!(incoming_call_b.next().await.unwrap().is_none());
6336
6337    executor.run_until_parked();
6338    assert_eq!(
6339        room_participants(&room_a, cx_a),
6340        RoomParticipants {
6341            remote: vec!["user_b".to_string()],
6342            pending: vec![],
6343        }
6344    );
6345    assert_eq!(
6346        room_participants(&room_b, cx_b),
6347        RoomParticipants {
6348            remote: vec!["user_a".to_string()],
6349            pending: vec![],
6350        }
6351    );
6352
6353    // Ensure User B sees User A's screenshare.
6354
6355    room_b.read_with(cx_b, |room, _| {
6356        assert_eq!(
6357            room.remote_participants()
6358                .get(&client_a.user_id().unwrap())
6359                .unwrap()
6360                .video_tracks
6361                .len(),
6362            1
6363        );
6364    });
6365}
6366
6367#[gpui::test]
6368async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
6369    let mut server = TestServer::start(cx.executor().clone()).await;
6370    let client_a = server.create_client(cx, "user_a").await;
6371    let (_workspace_a, cx) = client_a.build_test_workspace(cx).await;
6372
6373    cx.simulate_resize(size(px(300.), px(300.)));
6374
6375    cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
6376    cx.update(|window, _cx| window.refresh());
6377
6378    let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
6379    let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
6380
6381    assert!(
6382        tab_bounds.intersects(&new_tab_button_bounds),
6383        "Tab should overlap with the new tab button, if this is failing check if there's been a redesign!"
6384    );
6385
6386    cx.simulate_event(MouseDownEvent {
6387        button: MouseButton::Right,
6388        position: new_tab_button_bounds.center(),
6389        modifiers: Modifiers::default(),
6390        click_count: 1,
6391        first_mouse: false,
6392    });
6393
6394    // regression test that the right click menu for tabs does not open.
6395    assert!(cx.debug_bounds("MENU_ITEM-Close").is_none());
6396
6397    let tab_bounds = cx.debug_bounds("TAB-1").unwrap();
6398    cx.simulate_event(MouseDownEvent {
6399        button: MouseButton::Right,
6400        position: tab_bounds.center(),
6401        modifiers: Modifiers::default(),
6402        click_count: 1,
6403        first_mouse: false,
6404    });
6405    assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
6406}
6407
6408#[gpui::test]
6409async fn test_pane_split_left(cx: &mut TestAppContext) {
6410    let (_, client) = TestServer::start1(cx).await;
6411    let (workspace, cx) = client.build_test_workspace(cx).await;
6412
6413    cx.simulate_keystrokes("cmd-n");
6414    workspace.update(cx, |workspace, cx| {
6415        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
6416    });
6417    cx.simulate_keystrokes("cmd-k left");
6418    workspace.update(cx, |workspace, cx| {
6419        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6420    });
6421    cx.simulate_keystrokes("cmd-k");
6422    // sleep for longer than the timeout in keyboard shortcut handling
6423    // to verify that it doesn't fire in this case.
6424    cx.executor().advance_clock(Duration::from_secs(2));
6425    cx.simulate_keystrokes("left");
6426    workspace.update(cx, |workspace, cx| {
6427        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6428    });
6429}
6430
6431#[gpui::test]
6432async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
6433    let (mut server, client) = TestServer::start1(cx1).await;
6434    let channel1 = server.make_public_channel("channel1", &client, cx1).await;
6435    let channel2 = server.make_public_channel("channel2", &client, cx1).await;
6436
6437    join_channel(channel1, &client, cx1).await.unwrap();
6438    drop(client);
6439
6440    let client2 = server.create_client(cx2, "user_a").await;
6441    join_channel(channel2, &client2, cx2).await.unwrap();
6442}
6443
6444#[gpui::test]
6445async fn test_preview_tabs(cx: &mut TestAppContext) {
6446    let (_server, client) = TestServer::start1(cx).await;
6447    let (workspace, cx) = client.build_test_workspace(cx).await;
6448    let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
6449
6450    let worktree_id = project.update(cx, |project, cx| {
6451        project.worktrees(cx).next().unwrap().read(cx).id()
6452    });
6453
6454    let path_1 = ProjectPath {
6455        worktree_id,
6456        path: Path::new("1.txt").into(),
6457    };
6458    let path_2 = ProjectPath {
6459        worktree_id,
6460        path: Path::new("2.js").into(),
6461    };
6462    let path_3 = ProjectPath {
6463        worktree_id,
6464        path: Path::new("3.rs").into(),
6465    };
6466
6467    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6468
6469    let get_path = |pane: &Pane, idx: usize, cx: &App| {
6470        pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
6471    };
6472
6473    // Opening item 3 as a "permanent" tab
6474    workspace
6475        .update_in(cx, |workspace, window, cx| {
6476            workspace.open_path(path_3.clone(), None, false, window, cx)
6477        })
6478        .await
6479        .unwrap();
6480
6481    pane.update(cx, |pane, cx| {
6482        assert_eq!(pane.items_len(), 1);
6483        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6484        assert_eq!(pane.preview_item_id(), None);
6485
6486        assert!(!pane.can_navigate_backward());
6487        assert!(!pane.can_navigate_forward());
6488    });
6489
6490    // Open item 1 as preview
6491    workspace
6492        .update_in(cx, |workspace, window, cx| {
6493            workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
6494        })
6495        .await
6496        .unwrap();
6497
6498    pane.update(cx, |pane, cx| {
6499        assert_eq!(pane.items_len(), 2);
6500        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6501        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6502        assert_eq!(
6503            pane.preview_item_id(),
6504            Some(pane.items().nth(1).unwrap().item_id())
6505        );
6506
6507        assert!(pane.can_navigate_backward());
6508        assert!(!pane.can_navigate_forward());
6509    });
6510
6511    // Open item 2 as preview
6512    workspace
6513        .update_in(cx, |workspace, window, cx| {
6514            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6515        })
6516        .await
6517        .unwrap();
6518
6519    pane.update(cx, |pane, cx| {
6520        assert_eq!(pane.items_len(), 2);
6521        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6522        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6523        assert_eq!(
6524            pane.preview_item_id(),
6525            Some(pane.items().nth(1).unwrap().item_id())
6526        );
6527
6528        assert!(pane.can_navigate_backward());
6529        assert!(!pane.can_navigate_forward());
6530    });
6531
6532    // Going back should show item 1 as preview
6533    workspace
6534        .update_in(cx, |workspace, window, cx| {
6535            workspace.go_back(pane.downgrade(), window, cx)
6536        })
6537        .await
6538        .unwrap();
6539
6540    pane.update(cx, |pane, cx| {
6541        assert_eq!(pane.items_len(), 2);
6542        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6543        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6544        assert_eq!(
6545            pane.preview_item_id(),
6546            Some(pane.items().nth(1).unwrap().item_id())
6547        );
6548
6549        assert!(pane.can_navigate_backward());
6550        assert!(pane.can_navigate_forward());
6551    });
6552
6553    // Closing item 1
6554    pane.update_in(cx, |pane, window, cx| {
6555        pane.close_item_by_id(
6556            pane.active_item().unwrap().item_id(),
6557            workspace::SaveIntent::Skip,
6558            window,
6559            cx,
6560        )
6561    })
6562    .await
6563    .unwrap();
6564
6565    pane.update(cx, |pane, cx| {
6566        assert_eq!(pane.items_len(), 1);
6567        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6568        assert_eq!(pane.preview_item_id(), None);
6569
6570        assert!(pane.can_navigate_backward());
6571        assert!(!pane.can_navigate_forward());
6572    });
6573
6574    // Going back should show item 1 as preview
6575    workspace
6576        .update_in(cx, |workspace, window, cx| {
6577            workspace.go_back(pane.downgrade(), window, cx)
6578        })
6579        .await
6580        .unwrap();
6581
6582    pane.update(cx, |pane, cx| {
6583        assert_eq!(pane.items_len(), 2);
6584        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6585        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6586        assert_eq!(
6587            pane.preview_item_id(),
6588            Some(pane.items().nth(1).unwrap().item_id())
6589        );
6590
6591        assert!(pane.can_navigate_backward());
6592        assert!(pane.can_navigate_forward());
6593    });
6594
6595    // Close permanent tab
6596    pane.update_in(cx, |pane, window, cx| {
6597        let id = pane.items().next().unwrap().item_id();
6598        pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
6599    })
6600    .await
6601    .unwrap();
6602
6603    pane.update(cx, |pane, cx| {
6604        assert_eq!(pane.items_len(), 1);
6605        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6606        assert_eq!(
6607            pane.preview_item_id(),
6608            Some(pane.items().next().unwrap().item_id())
6609        );
6610
6611        assert!(pane.can_navigate_backward());
6612        assert!(pane.can_navigate_forward());
6613    });
6614
6615    // Split pane to the right
6616    pane.update(cx, |pane, cx| {
6617        pane.split(workspace::SplitDirection::Right, cx);
6618    });
6619
6620    let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6621
6622    pane.update(cx, |pane, cx| {
6623        assert_eq!(pane.items_len(), 1);
6624        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6625        assert_eq!(
6626            pane.preview_item_id(),
6627            Some(pane.items().next().unwrap().item_id())
6628        );
6629
6630        assert!(pane.can_navigate_backward());
6631        assert!(pane.can_navigate_forward());
6632    });
6633
6634    right_pane.update(cx, |pane, cx| {
6635        assert_eq!(pane.items_len(), 1);
6636        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6637        assert_eq!(pane.preview_item_id(), None);
6638
6639        assert!(!pane.can_navigate_backward());
6640        assert!(!pane.can_navigate_forward());
6641    });
6642
6643    // Open item 2 as preview in right pane
6644    workspace
6645        .update_in(cx, |workspace, window, cx| {
6646            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6647        })
6648        .await
6649        .unwrap();
6650
6651    pane.update(cx, |pane, cx| {
6652        assert_eq!(pane.items_len(), 1);
6653        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6654        assert_eq!(
6655            pane.preview_item_id(),
6656            Some(pane.items().next().unwrap().item_id())
6657        );
6658
6659        assert!(pane.can_navigate_backward());
6660        assert!(pane.can_navigate_forward());
6661    });
6662
6663    right_pane.update(cx, |pane, cx| {
6664        assert_eq!(pane.items_len(), 2);
6665        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6666        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6667        assert_eq!(
6668            pane.preview_item_id(),
6669            Some(pane.items().nth(1).unwrap().item_id())
6670        );
6671
6672        assert!(pane.can_navigate_backward());
6673        assert!(!pane.can_navigate_forward());
6674    });
6675
6676    // Focus left pane
6677    workspace.update_in(cx, |workspace, window, cx| {
6678        workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
6679    });
6680
6681    // Open item 2 as preview in left pane
6682    workspace
6683        .update_in(cx, |workspace, window, cx| {
6684            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6685        })
6686        .await
6687        .unwrap();
6688
6689    pane.update(cx, |pane, cx| {
6690        assert_eq!(pane.items_len(), 1);
6691        assert_eq!(get_path(pane, 0, cx), path_2.clone());
6692        assert_eq!(
6693            pane.preview_item_id(),
6694            Some(pane.items().next().unwrap().item_id())
6695        );
6696
6697        assert!(pane.can_navigate_backward());
6698        assert!(!pane.can_navigate_forward());
6699    });
6700
6701    right_pane.update(cx, |pane, cx| {
6702        assert_eq!(pane.items_len(), 2);
6703        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6704        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6705        assert_eq!(
6706            pane.preview_item_id(),
6707            Some(pane.items().nth(1).unwrap().item_id())
6708        );
6709
6710        assert!(pane.can_navigate_backward());
6711        assert!(!pane.can_navigate_forward());
6712    });
6713}
6714
6715#[gpui::test(iterations = 10)]
6716async fn test_context_collaboration_with_reconnect(
6717    executor: BackgroundExecutor,
6718    cx_a: &mut TestAppContext,
6719    cx_b: &mut TestAppContext,
6720) {
6721    let mut server = TestServer::start(executor.clone()).await;
6722    let client_a = server.create_client(cx_a, "user_a").await;
6723    let client_b = server.create_client(cx_b, "user_b").await;
6724    server
6725        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6726        .await;
6727    let active_call_a = cx_a.read(ActiveCall::global);
6728
6729    client_a.fs().insert_tree("/a", Default::default()).await;
6730    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6731    let project_id = active_call_a
6732        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6733        .await
6734        .unwrap();
6735    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6736
6737    // Client A sees that a guest has joined.
6738    executor.run_until_parked();
6739
6740    project_a.read_with(cx_a, |project, _| {
6741        assert_eq!(project.collaborators().len(), 1);
6742    });
6743    project_b.read_with(cx_b, |project, _| {
6744        assert_eq!(project.collaborators().len(), 1);
6745    });
6746
6747    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
6748    let context_store_a = cx_a
6749        .update(|cx| {
6750            ContextStore::new(
6751                project_a.clone(),
6752                prompt_builder.clone(),
6753                Arc::new(SlashCommandWorkingSet::default()),
6754                cx,
6755            )
6756        })
6757        .await
6758        .unwrap();
6759    let context_store_b = cx_b
6760        .update(|cx| {
6761            ContextStore::new(
6762                project_b.clone(),
6763                prompt_builder.clone(),
6764                Arc::new(SlashCommandWorkingSet::default()),
6765                cx,
6766            )
6767        })
6768        .await
6769        .unwrap();
6770
6771    // Client A creates a new chats.
6772    let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
6773    executor.run_until_parked();
6774
6775    // Client B retrieves host's contexts and joins one.
6776    let context_b = context_store_b
6777        .update(cx_b, |store, cx| {
6778            let host_contexts = store.host_contexts().to_vec();
6779            assert_eq!(host_contexts.len(), 1);
6780            store.open_remote_context(host_contexts[0].id.clone(), cx)
6781        })
6782        .await
6783        .unwrap();
6784
6785    // Host and guest make changes
6786    context_a.update(cx_a, |context, cx| {
6787        context.buffer().update(cx, |buffer, cx| {
6788            buffer.edit([(0..0, "Host change\n")], None, cx)
6789        })
6790    });
6791    context_b.update(cx_b, |context, cx| {
6792        context.buffer().update(cx, |buffer, cx| {
6793            buffer.edit([(0..0, "Guest change\n")], None, cx)
6794        })
6795    });
6796    executor.run_until_parked();
6797    assert_eq!(
6798        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6799        "Guest change\nHost change\n"
6800    );
6801    assert_eq!(
6802        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6803        "Guest change\nHost change\n"
6804    );
6805
6806    // Disconnect client A and make some changes while disconnected.
6807    server.disconnect_client(client_a.peer_id().unwrap());
6808    server.forbid_connections();
6809    context_a.update(cx_a, |context, cx| {
6810        context.buffer().update(cx, |buffer, cx| {
6811            buffer.edit([(0..0, "Host offline change\n")], None, cx)
6812        })
6813    });
6814    context_b.update(cx_b, |context, cx| {
6815        context.buffer().update(cx, |buffer, cx| {
6816            buffer.edit([(0..0, "Guest offline change\n")], None, cx)
6817        })
6818    });
6819    executor.run_until_parked();
6820    assert_eq!(
6821        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6822        "Host offline change\nGuest change\nHost change\n"
6823    );
6824    assert_eq!(
6825        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6826        "Guest offline change\nGuest change\nHost change\n"
6827    );
6828
6829    // Allow client A to reconnect and verify that contexts converge.
6830    server.allow_connections();
6831    executor.advance_clock(RECEIVE_TIMEOUT);
6832    assert_eq!(
6833        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6834        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6835    );
6836    assert_eq!(
6837        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6838        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6839    );
6840
6841    // Client A disconnects without being able to reconnect. Context B becomes readonly.
6842    server.forbid_connections();
6843    server.disconnect_client(client_a.peer_id().unwrap());
6844    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6845    context_b.read_with(cx_b, |context, cx| {
6846        assert!(context.buffer().read(cx).read_only());
6847    });
6848}
6849
6850#[gpui::test]
6851async fn test_remote_git_branches(
6852    executor: BackgroundExecutor,
6853    cx_a: &mut TestAppContext,
6854    cx_b: &mut TestAppContext,
6855) {
6856    let mut server = TestServer::start(executor.clone()).await;
6857    let client_a = server.create_client(cx_a, "user_a").await;
6858    let client_b = server.create_client(cx_b, "user_b").await;
6859    server
6860        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6861        .await;
6862    let active_call_a = cx_a.read(ActiveCall::global);
6863
6864    client_a
6865        .fs()
6866        .insert_tree("/project", serde_json::json!({ ".git":{} }))
6867        .await;
6868    let branches = ["main", "dev", "feature-1"];
6869    client_a
6870        .fs()
6871        .insert_branches(Path::new("/project/.git"), &branches);
6872    let branches_set = branches
6873        .into_iter()
6874        .map(ToString::to_string)
6875        .collect::<HashSet<_>>();
6876
6877    let (project_a, _) = client_a.build_local_project("/project", cx_a).await;
6878
6879    let project_id = active_call_a
6880        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6881        .await
6882        .unwrap();
6883    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6884
6885    // Client A sees that a guest has joined and the repo has been populated
6886    executor.run_until_parked();
6887
6888    let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
6889
6890    let branches_b = cx_b
6891        .update(|cx| repo_b.update(cx, |repository, _| repository.branches()))
6892        .await
6893        .unwrap()
6894        .unwrap();
6895
6896    let new_branch = branches[2];
6897
6898    let branches_b = branches_b
6899        .into_iter()
6900        .map(|branch| branch.name().to_string())
6901        .collect::<HashSet<_>>();
6902
6903    assert_eq!(branches_b, branches_set);
6904
6905    cx_b.update(|cx| {
6906        repo_b.update(cx, |repository, _cx| {
6907            repository.change_branch(new_branch.to_string())
6908        })
6909    })
6910    .await
6911    .unwrap()
6912    .unwrap();
6913
6914    executor.run_until_parked();
6915
6916    let host_branch = cx_a.update(|cx| {
6917        project_a.update(cx, |project, cx| {
6918            project
6919                .repositories(cx)
6920                .values()
6921                .next()
6922                .unwrap()
6923                .read(cx)
6924                .branch
6925                .as_ref()
6926                .unwrap()
6927                .clone()
6928        })
6929    });
6930
6931    assert_eq!(host_branch.name(), branches[2]);
6932
6933    // Also try creating a new branch
6934    cx_b.update(|cx| {
6935        repo_b.update(cx, |repository, _cx| {
6936            repository.create_branch("totally-new-branch".to_string())
6937        })
6938    })
6939    .await
6940    .unwrap()
6941    .unwrap();
6942
6943    cx_b.update(|cx| {
6944        repo_b.update(cx, |repository, _cx| {
6945            repository.change_branch("totally-new-branch".to_string())
6946        })
6947    })
6948    .await
6949    .unwrap()
6950    .unwrap();
6951
6952    executor.run_until_parked();
6953
6954    let host_branch = cx_a.update(|cx| {
6955        project_a.update(cx, |project, cx| {
6956            project
6957                .repositories(cx)
6958                .values()
6959                .next()
6960                .unwrap()
6961                .read(cx)
6962                .branch
6963                .as_ref()
6964                .unwrap()
6965                .clone()
6966        })
6967    });
6968
6969    assert_eq!(host_branch.name(), "totally-new-branch");
6970}