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::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, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
  24    LanguageMatcher, 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, uri};
  55use workspace::Pane;
  56
  57#[ctor::ctor]
  58fn init_logger() {
  59    zlog::init_test();
  60}
  61
  62#[gpui::test(iterations = 10)]
  63async fn test_database_failure_during_client_reconnection(
  64    executor: BackgroundExecutor,
  65    cx: &mut TestAppContext,
  66) {
  67    let mut server = TestServer::start(executor.clone()).await;
  68    let client = server.create_client(cx, "user_a").await;
  69
  70    // Keep disconnecting the client until a database failure prevents it from
  71    // reconnecting.
  72    server.test_db.set_query_failure_probability(0.3);
  73    loop {
  74        server.disconnect_client(client.peer_id().unwrap());
  75        executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  76        if !client.status().borrow().is_connected() {
  77            break;
  78        }
  79    }
  80
  81    // Make the database healthy again and ensure the client can finally connect.
  82    server.test_db.set_query_failure_probability(0.);
  83    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  84    assert!(
  85        matches!(*client.status().borrow(), client::Status::Connected { .. }),
  86        "status was {:?}",
  87        *client.status().borrow()
  88    );
  89}
  90
  91#[gpui::test(iterations = 10)]
  92async fn test_basic_calls(
  93    executor: BackgroundExecutor,
  94    cx_a: &mut TestAppContext,
  95    cx_b: &mut TestAppContext,
  96    cx_b2: &mut TestAppContext,
  97    cx_c: &mut TestAppContext,
  98) {
  99    let mut server = TestServer::start(executor.clone()).await;
 100
 101    let client_a = server.create_client(cx_a, "user_a").await;
 102    let client_b = server.create_client(cx_b, "user_b").await;
 103    let client_c = server.create_client(cx_c, "user_c").await;
 104    server
 105        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 106        .await;
 107
 108    let active_call_a = cx_a.read(ActiveCall::global);
 109    let active_call_b = cx_b.read(ActiveCall::global);
 110    let active_call_c = cx_c.read(ActiveCall::global);
 111
 112    // Call user B from client A.
 113    active_call_a
 114        .update(cx_a, |call, cx| {
 115            call.invite(client_b.user_id().unwrap(), None, cx)
 116        })
 117        .await
 118        .unwrap();
 119    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 120    executor.run_until_parked();
 121    assert_eq!(
 122        room_participants(&room_a, cx_a),
 123        RoomParticipants {
 124            remote: Default::default(),
 125            pending: vec!["user_b".to_string()]
 126        }
 127    );
 128
 129    // User B receives the call.
 130
 131    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 132    let call_b = incoming_call_b.next().await.unwrap().unwrap();
 133    assert_eq!(call_b.calling_user.github_login, "user_a");
 134
 135    // User B connects via another client and also receives a ring on the newly-connected client.
 136    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 137    let active_call_b2 = cx_b2.read(ActiveCall::global);
 138
 139    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
 140    executor.run_until_parked();
 141    let call_b2 = incoming_call_b2.next().await.unwrap().unwrap();
 142    assert_eq!(call_b2.calling_user.github_login, "user_a");
 143
 144    // User B joins the room using the first client.
 145    active_call_b
 146        .update(cx_b, |call, cx| call.accept_incoming(cx))
 147        .await
 148        .unwrap();
 149
 150    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 151    assert!(incoming_call_b.next().await.unwrap().is_none());
 152
 153    executor.run_until_parked();
 154    assert_eq!(
 155        room_participants(&room_a, cx_a),
 156        RoomParticipants {
 157            remote: vec!["user_b".to_string()],
 158            pending: Default::default()
 159        }
 160    );
 161    assert_eq!(
 162        room_participants(&room_b, cx_b),
 163        RoomParticipants {
 164            remote: vec!["user_a".to_string()],
 165            pending: Default::default()
 166        }
 167    );
 168
 169    // Call user C from client B.
 170
 171    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 172    active_call_b
 173        .update(cx_b, |call, cx| {
 174            call.invite(client_c.user_id().unwrap(), None, cx)
 175        })
 176        .await
 177        .unwrap();
 178
 179    executor.run_until_parked();
 180    assert_eq!(
 181        room_participants(&room_a, cx_a),
 182        RoomParticipants {
 183            remote: vec!["user_b".to_string()],
 184            pending: vec!["user_c".to_string()]
 185        }
 186    );
 187    assert_eq!(
 188        room_participants(&room_b, cx_b),
 189        RoomParticipants {
 190            remote: vec!["user_a".to_string()],
 191            pending: vec!["user_c".to_string()]
 192        }
 193    );
 194
 195    // User C receives the call, but declines it.
 196    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 197    assert_eq!(call_c.calling_user.github_login, "user_b");
 198    active_call_c.update(cx_c, |call, cx| call.decline_incoming(cx).unwrap());
 199    assert!(incoming_call_c.next().await.unwrap().is_none());
 200
 201    executor.run_until_parked();
 202    assert_eq!(
 203        room_participants(&room_a, cx_a),
 204        RoomParticipants {
 205            remote: vec!["user_b".to_string()],
 206            pending: Default::default()
 207        }
 208    );
 209    assert_eq!(
 210        room_participants(&room_b, cx_b),
 211        RoomParticipants {
 212            remote: vec!["user_a".to_string()],
 213            pending: Default::default()
 214        }
 215    );
 216
 217    // Call user C again from user A.
 218    active_call_a
 219        .update(cx_a, |call, cx| {
 220            call.invite(client_c.user_id().unwrap(), None, cx)
 221        })
 222        .await
 223        .unwrap();
 224
 225    executor.run_until_parked();
 226    assert_eq!(
 227        room_participants(&room_a, cx_a),
 228        RoomParticipants {
 229            remote: vec!["user_b".to_string()],
 230            pending: vec!["user_c".to_string()]
 231        }
 232    );
 233    assert_eq!(
 234        room_participants(&room_b, cx_b),
 235        RoomParticipants {
 236            remote: vec!["user_a".to_string()],
 237            pending: vec!["user_c".to_string()]
 238        }
 239    );
 240
 241    // User C accepts the call.
 242    let call_c = incoming_call_c.next().await.unwrap().unwrap();
 243    assert_eq!(call_c.calling_user.github_login, "user_a");
 244    active_call_c
 245        .update(cx_c, |call, cx| call.accept_incoming(cx))
 246        .await
 247        .unwrap();
 248    assert!(incoming_call_c.next().await.unwrap().is_none());
 249
 250    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 251
 252    executor.run_until_parked();
 253    assert_eq!(
 254        room_participants(&room_a, cx_a),
 255        RoomParticipants {
 256            remote: vec!["user_b".to_string(), "user_c".to_string()],
 257            pending: Default::default()
 258        }
 259    );
 260    assert_eq!(
 261        room_participants(&room_b, cx_b),
 262        RoomParticipants {
 263            remote: vec!["user_a".to_string(), "user_c".to_string()],
 264            pending: Default::default()
 265        }
 266    );
 267    assert_eq!(
 268        room_participants(&room_c, cx_c),
 269        RoomParticipants {
 270            remote: vec!["user_a".to_string(), "user_b".to_string()],
 271            pending: Default::default()
 272        }
 273    );
 274
 275    // User A shares their screen
 276    let display = gpui::TestScreenCaptureSource::new();
 277    let events_b = active_call_events(cx_b);
 278    let events_c = active_call_events(cx_c);
 279    cx_a.set_screen_capture_sources(vec![display]);
 280    active_call_a
 281        .update(cx_a, |call, cx| {
 282            call.room()
 283                .unwrap()
 284                .update(cx, |room, cx| room.share_screen(cx))
 285        })
 286        .await
 287        .unwrap();
 288
 289    executor.run_until_parked();
 290
 291    // User B observes the remote screen sharing track.
 292    assert_eq!(events_b.borrow().len(), 1);
 293    let event_b = events_b.borrow().first().unwrap().clone();
 294    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_b {
 295        assert_eq!(participant_id, client_a.peer_id().unwrap());
 296
 297        room_b.read_with(cx_b, |room, _| {
 298            assert_eq!(
 299                room.remote_participants()[&client_a.user_id().unwrap()]
 300                    .video_tracks
 301                    .len(),
 302                1
 303            );
 304        });
 305    } else {
 306        panic!("unexpected event")
 307    }
 308
 309    // User C observes the remote screen sharing track.
 310    assert_eq!(events_c.borrow().len(), 1);
 311    let event_c = events_c.borrow().first().unwrap().clone();
 312    if let call::room::Event::RemoteVideoTracksChanged { participant_id } = event_c {
 313        assert_eq!(participant_id, client_a.peer_id().unwrap());
 314
 315        room_c.read_with(cx_c, |room, _| {
 316            assert_eq!(
 317                room.remote_participants()[&client_a.user_id().unwrap()]
 318                    .video_tracks
 319                    .len(),
 320                1
 321            );
 322        });
 323    } else {
 324        panic!("unexpected event")
 325    }
 326
 327    // User A leaves the room.
 328    active_call_a
 329        .update(cx_a, |call, cx| {
 330            let hang_up = call.hang_up(cx);
 331            assert!(call.room().is_none());
 332            hang_up
 333        })
 334        .await
 335        .unwrap();
 336    executor.run_until_parked();
 337    assert_eq!(
 338        room_participants(&room_a, cx_a),
 339        RoomParticipants {
 340            remote: Default::default(),
 341            pending: Default::default()
 342        }
 343    );
 344    assert_eq!(
 345        room_participants(&room_b, cx_b),
 346        RoomParticipants {
 347            remote: vec!["user_c".to_string()],
 348            pending: Default::default()
 349        }
 350    );
 351    assert_eq!(
 352        room_participants(&room_c, cx_c),
 353        RoomParticipants {
 354            remote: vec!["user_b".to_string()],
 355            pending: Default::default()
 356        }
 357    );
 358
 359    // User B gets disconnected from the LiveKit server, which causes them
 360    // to automatically leave the room. User C leaves the room as well because
 361    // nobody else is in there.
 362    server
 363        .test_livekit_server
 364        .disconnect_client(client_b.user_id().unwrap().to_string())
 365        .await;
 366    executor.run_until_parked();
 367
 368    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 369
 370    active_call_c.read_with(cx_c, |call, _| assert!(call.room().is_none()));
 371    assert_eq!(
 372        room_participants(&room_a, cx_a),
 373        RoomParticipants {
 374            remote: Default::default(),
 375            pending: Default::default()
 376        }
 377    );
 378    assert_eq!(
 379        room_participants(&room_b, cx_b),
 380        RoomParticipants {
 381            remote: Default::default(),
 382            pending: Default::default()
 383        }
 384    );
 385    assert_eq!(
 386        room_participants(&room_c, cx_c),
 387        RoomParticipants {
 388            remote: Default::default(),
 389            pending: Default::default()
 390        }
 391    );
 392}
 393
 394#[gpui::test(iterations = 10)]
 395async fn test_calling_multiple_users_simultaneously(
 396    executor: BackgroundExecutor,
 397    cx_a: &mut TestAppContext,
 398    cx_b: &mut TestAppContext,
 399    cx_c: &mut TestAppContext,
 400    cx_d: &mut TestAppContext,
 401) {
 402    let mut server = TestServer::start(executor.clone()).await;
 403
 404    let client_a = server.create_client(cx_a, "user_a").await;
 405    let client_b = server.create_client(cx_b, "user_b").await;
 406    let client_c = server.create_client(cx_c, "user_c").await;
 407    let client_d = server.create_client(cx_d, "user_d").await;
 408    server
 409        .make_contacts(&mut [
 410            (&client_a, cx_a),
 411            (&client_b, cx_b),
 412            (&client_c, cx_c),
 413            (&client_d, cx_d),
 414        ])
 415        .await;
 416
 417    let active_call_a = cx_a.read(ActiveCall::global);
 418    let active_call_b = cx_b.read(ActiveCall::global);
 419    let active_call_c = cx_c.read(ActiveCall::global);
 420    let active_call_d = cx_d.read(ActiveCall::global);
 421
 422    // Simultaneously call user B and user C from client A.
 423    let b_invite = active_call_a.update(cx_a, |call, cx| {
 424        call.invite(client_b.user_id().unwrap(), None, cx)
 425    });
 426    let c_invite = active_call_a.update(cx_a, |call, cx| {
 427        call.invite(client_c.user_id().unwrap(), None, cx)
 428    });
 429    b_invite.await.unwrap();
 430    c_invite.await.unwrap();
 431
 432    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 433    executor.run_until_parked();
 434    assert_eq!(
 435        room_participants(&room_a, cx_a),
 436        RoomParticipants {
 437            remote: Default::default(),
 438            pending: vec!["user_b".to_string(), "user_c".to_string()]
 439        }
 440    );
 441
 442    // Call client D from client A.
 443    active_call_a
 444        .update(cx_a, |call, cx| {
 445            call.invite(client_d.user_id().unwrap(), None, cx)
 446        })
 447        .await
 448        .unwrap();
 449    executor.run_until_parked();
 450    assert_eq!(
 451        room_participants(&room_a, cx_a),
 452        RoomParticipants {
 453            remote: Default::default(),
 454            pending: vec![
 455                "user_b".to_string(),
 456                "user_c".to_string(),
 457                "user_d".to_string()
 458            ]
 459        }
 460    );
 461
 462    // Accept the call on all clients simultaneously.
 463    let accept_b = active_call_b.update(cx_b, |call, cx| call.accept_incoming(cx));
 464    let accept_c = active_call_c.update(cx_c, |call, cx| call.accept_incoming(cx));
 465    let accept_d = active_call_d.update(cx_d, |call, cx| call.accept_incoming(cx));
 466    accept_b.await.unwrap();
 467    accept_c.await.unwrap();
 468    accept_d.await.unwrap();
 469
 470    executor.run_until_parked();
 471
 472    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 473
 474    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 475
 476    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
 477    assert_eq!(
 478        room_participants(&room_a, cx_a),
 479        RoomParticipants {
 480            remote: vec![
 481                "user_b".to_string(),
 482                "user_c".to_string(),
 483                "user_d".to_string(),
 484            ],
 485            pending: Default::default()
 486        }
 487    );
 488    assert_eq!(
 489        room_participants(&room_b, cx_b),
 490        RoomParticipants {
 491            remote: vec![
 492                "user_a".to_string(),
 493                "user_c".to_string(),
 494                "user_d".to_string(),
 495            ],
 496            pending: Default::default()
 497        }
 498    );
 499    assert_eq!(
 500        room_participants(&room_c, cx_c),
 501        RoomParticipants {
 502            remote: vec![
 503                "user_a".to_string(),
 504                "user_b".to_string(),
 505                "user_d".to_string(),
 506            ],
 507            pending: Default::default()
 508        }
 509    );
 510    assert_eq!(
 511        room_participants(&room_d, cx_d),
 512        RoomParticipants {
 513            remote: vec![
 514                "user_a".to_string(),
 515                "user_b".to_string(),
 516                "user_c".to_string(),
 517            ],
 518            pending: Default::default()
 519        }
 520    );
 521}
 522
 523#[gpui::test(iterations = 10)]
 524async fn test_joining_channels_and_calling_multiple_users_simultaneously(
 525    executor: BackgroundExecutor,
 526    cx_a: &mut TestAppContext,
 527    cx_b: &mut TestAppContext,
 528    cx_c: &mut TestAppContext,
 529) {
 530    let mut server = TestServer::start(executor.clone()).await;
 531
 532    let client_a = server.create_client(cx_a, "user_a").await;
 533    let client_b = server.create_client(cx_b, "user_b").await;
 534    let client_c = server.create_client(cx_c, "user_c").await;
 535    server
 536        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 537        .await;
 538
 539    let channel_1 = server
 540        .make_channel(
 541            "channel1",
 542            None,
 543            (&client_a, cx_a),
 544            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 545        )
 546        .await;
 547
 548    let channel_2 = server
 549        .make_channel(
 550            "channel2",
 551            None,
 552            (&client_a, cx_a),
 553            &mut [(&client_b, cx_b), (&client_c, cx_c)],
 554        )
 555        .await;
 556
 557    let active_call_a = cx_a.read(ActiveCall::global);
 558
 559    // Simultaneously join channel 1 and then channel 2
 560    active_call_a
 561        .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
 562        .detach();
 563    let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
 564
 565    join_channel_2.await.unwrap();
 566
 567    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 568    executor.run_until_parked();
 569
 570    assert_eq!(channel_id(&room_a, cx_a), Some(channel_2));
 571
 572    // Leave the room
 573    active_call_a
 574        .update(cx_a, |call, cx| call.hang_up(cx))
 575        .await
 576        .unwrap();
 577
 578    // Initiating invites and then joining a channel should fail gracefully
 579    let b_invite = active_call_a.update(cx_a, |call, cx| {
 580        call.invite(client_b.user_id().unwrap(), None, cx)
 581    });
 582    let c_invite = active_call_a.update(cx_a, |call, cx| {
 583        call.invite(client_c.user_id().unwrap(), None, cx)
 584    });
 585
 586    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 587
 588    b_invite.await.unwrap();
 589    c_invite.await.unwrap();
 590    join_channel.await.unwrap();
 591
 592    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 593    executor.run_until_parked();
 594
 595    assert_eq!(
 596        room_participants(&room_a, cx_a),
 597        RoomParticipants {
 598            remote: Default::default(),
 599            pending: vec!["user_b".to_string(), "user_c".to_string()]
 600        }
 601    );
 602
 603    assert_eq!(channel_id(&room_a, cx_a), None);
 604
 605    // Leave the room
 606    active_call_a
 607        .update(cx_a, |call, cx| call.hang_up(cx))
 608        .await
 609        .unwrap();
 610
 611    // Simultaneously join channel 1 and call user B and user C from client A.
 612    let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
 613
 614    let b_invite = active_call_a.update(cx_a, |call, cx| {
 615        call.invite(client_b.user_id().unwrap(), None, cx)
 616    });
 617    let c_invite = active_call_a.update(cx_a, |call, cx| {
 618        call.invite(client_c.user_id().unwrap(), None, cx)
 619    });
 620
 621    join_channel.await.unwrap();
 622    b_invite.await.unwrap();
 623    c_invite.await.unwrap();
 624
 625    active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 626    executor.run_until_parked();
 627}
 628
 629#[gpui::test(iterations = 10)]
 630async fn test_room_uniqueness(
 631    executor: BackgroundExecutor,
 632    cx_a: &mut TestAppContext,
 633    cx_a2: &mut TestAppContext,
 634    cx_b: &mut TestAppContext,
 635    cx_b2: &mut TestAppContext,
 636    cx_c: &mut TestAppContext,
 637) {
 638    let mut server = TestServer::start(executor.clone()).await;
 639    let client_a = server.create_client(cx_a, "user_a").await;
 640    let _client_a2 = server.create_client(cx_a2, "user_a").await;
 641    let client_b = server.create_client(cx_b, "user_b").await;
 642    let _client_b2 = server.create_client(cx_b2, "user_b").await;
 643    let client_c = server.create_client(cx_c, "user_c").await;
 644    server
 645        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 646        .await;
 647
 648    let active_call_a = cx_a.read(ActiveCall::global);
 649    let active_call_a2 = cx_a2.read(ActiveCall::global);
 650    let active_call_b = cx_b.read(ActiveCall::global);
 651    let active_call_b2 = cx_b2.read(ActiveCall::global);
 652    let active_call_c = cx_c.read(ActiveCall::global);
 653
 654    // Call user B from client A.
 655    active_call_a
 656        .update(cx_a, |call, cx| {
 657            call.invite(client_b.user_id().unwrap(), None, cx)
 658        })
 659        .await
 660        .unwrap();
 661
 662    // Ensure a new room can't be created given user A just created one.
 663    active_call_a2
 664        .update(cx_a2, |call, cx| {
 665            call.invite(client_c.user_id().unwrap(), None, cx)
 666        })
 667        .await
 668        .unwrap_err();
 669
 670    active_call_a2.read_with(cx_a2, |call, _| assert!(call.room().is_none()));
 671
 672    // User B receives the call from user A.
 673
 674    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 675    let call_b1 = incoming_call_b.next().await.unwrap().unwrap();
 676    assert_eq!(call_b1.calling_user.github_login, "user_a");
 677
 678    // Ensure calling users A and B from client C fails.
 679    active_call_c
 680        .update(cx_c, |call, cx| {
 681            call.invite(client_a.user_id().unwrap(), None, cx)
 682        })
 683        .await
 684        .unwrap_err();
 685    active_call_c
 686        .update(cx_c, |call, cx| {
 687            call.invite(client_b.user_id().unwrap(), None, cx)
 688        })
 689        .await
 690        .unwrap_err();
 691
 692    // Ensure User B can't create a room while they still have an incoming call.
 693    active_call_b2
 694        .update(cx_b2, |call, cx| {
 695            call.invite(client_c.user_id().unwrap(), None, cx)
 696        })
 697        .await
 698        .unwrap_err();
 699
 700    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 701
 702    // User B joins the room and calling them after they've joined still fails.
 703    active_call_b
 704        .update(cx_b, |call, cx| call.accept_incoming(cx))
 705        .await
 706        .unwrap();
 707    active_call_c
 708        .update(cx_c, |call, cx| {
 709            call.invite(client_b.user_id().unwrap(), None, cx)
 710        })
 711        .await
 712        .unwrap_err();
 713
 714    // Ensure User B can't create a room while they belong to another room.
 715    active_call_b2
 716        .update(cx_b2, |call, cx| {
 717            call.invite(client_c.user_id().unwrap(), None, cx)
 718        })
 719        .await
 720        .unwrap_err();
 721
 722    active_call_b2.read_with(cx_b2, |call, _| assert!(call.room().is_none()));
 723
 724    // Client C can successfully call client B after client B leaves the room.
 725    active_call_b
 726        .update(cx_b, |call, cx| call.hang_up(cx))
 727        .await
 728        .unwrap();
 729    executor.run_until_parked();
 730    active_call_c
 731        .update(cx_c, |call, cx| {
 732            call.invite(client_b.user_id().unwrap(), None, cx)
 733        })
 734        .await
 735        .unwrap();
 736    executor.run_until_parked();
 737    let call_b2 = incoming_call_b.next().await.unwrap().unwrap();
 738    assert_eq!(call_b2.calling_user.github_login, "user_c");
 739}
 740
 741#[gpui::test(iterations = 10)]
 742async fn test_client_disconnecting_from_room(
 743    executor: BackgroundExecutor,
 744    cx_a: &mut TestAppContext,
 745    cx_b: &mut TestAppContext,
 746) {
 747    let mut server = TestServer::start(executor.clone()).await;
 748    let client_a = server.create_client(cx_a, "user_a").await;
 749    let client_b = server.create_client(cx_b, "user_b").await;
 750    server
 751        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 752        .await;
 753
 754    let active_call_a = cx_a.read(ActiveCall::global);
 755    let active_call_b = cx_b.read(ActiveCall::global);
 756
 757    // Call user B from client A.
 758    active_call_a
 759        .update(cx_a, |call, cx| {
 760            call.invite(client_b.user_id().unwrap(), None, cx)
 761        })
 762        .await
 763        .unwrap();
 764
 765    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 766
 767    // User B receives the call and joins the room.
 768
 769    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 770    incoming_call_b.next().await.unwrap().unwrap();
 771    active_call_b
 772        .update(cx_b, |call, cx| call.accept_incoming(cx))
 773        .await
 774        .unwrap();
 775
 776    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 777    executor.run_until_parked();
 778    assert_eq!(
 779        room_participants(&room_a, cx_a),
 780        RoomParticipants {
 781            remote: vec!["user_b".to_string()],
 782            pending: Default::default()
 783        }
 784    );
 785    assert_eq!(
 786        room_participants(&room_b, cx_b),
 787        RoomParticipants {
 788            remote: vec!["user_a".to_string()],
 789            pending: Default::default()
 790        }
 791    );
 792
 793    // User A automatically reconnects to the room upon disconnection.
 794    server.disconnect_client(client_a.peer_id().unwrap());
 795    executor.advance_clock(RECEIVE_TIMEOUT);
 796    executor.run_until_parked();
 797    assert_eq!(
 798        room_participants(&room_a, cx_a),
 799        RoomParticipants {
 800            remote: vec!["user_b".to_string()],
 801            pending: Default::default()
 802        }
 803    );
 804    assert_eq!(
 805        room_participants(&room_b, cx_b),
 806        RoomParticipants {
 807            remote: vec!["user_a".to_string()],
 808            pending: Default::default()
 809        }
 810    );
 811
 812    // When user A disconnects, both client A and B clear their room on the active call.
 813    server.forbid_connections();
 814    server.disconnect_client(client_a.peer_id().unwrap());
 815    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 816
 817    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
 818
 819    active_call_b.read_with(cx_b, |call, _| assert!(call.room().is_none()));
 820    assert_eq!(
 821        room_participants(&room_a, cx_a),
 822        RoomParticipants {
 823            remote: Default::default(),
 824            pending: Default::default()
 825        }
 826    );
 827    assert_eq!(
 828        room_participants(&room_b, cx_b),
 829        RoomParticipants {
 830            remote: Default::default(),
 831            pending: Default::default()
 832        }
 833    );
 834
 835    // Allow user A to reconnect to the server.
 836    server.allow_connections();
 837    executor.advance_clock(RECEIVE_TIMEOUT);
 838
 839    // Call user B again from client A.
 840    active_call_a
 841        .update(cx_a, |call, cx| {
 842            call.invite(client_b.user_id().unwrap(), None, cx)
 843        })
 844        .await
 845        .unwrap();
 846
 847    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 848
 849    // User B receives the call and joins the room.
 850
 851    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 852    incoming_call_b.next().await.unwrap().unwrap();
 853    active_call_b
 854        .update(cx_b, |call, cx| call.accept_incoming(cx))
 855        .await
 856        .unwrap();
 857
 858    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 859    executor.run_until_parked();
 860    assert_eq!(
 861        room_participants(&room_a, cx_a),
 862        RoomParticipants {
 863            remote: vec!["user_b".to_string()],
 864            pending: Default::default()
 865        }
 866    );
 867    assert_eq!(
 868        room_participants(&room_b, cx_b),
 869        RoomParticipants {
 870            remote: vec!["user_a".to_string()],
 871            pending: Default::default()
 872        }
 873    );
 874
 875    // User B gets disconnected from the LiveKit server, which causes it
 876    // to automatically leave the room.
 877    server
 878        .test_livekit_server
 879        .disconnect_client(client_b.user_id().unwrap().to_string())
 880        .await;
 881    executor.run_until_parked();
 882    active_call_a.update(cx_a, |call, _| assert!(call.room().is_none()));
 883    active_call_b.update(cx_b, |call, _| assert!(call.room().is_none()));
 884    assert_eq!(
 885        room_participants(&room_a, cx_a),
 886        RoomParticipants {
 887            remote: Default::default(),
 888            pending: Default::default()
 889        }
 890    );
 891    assert_eq!(
 892        room_participants(&room_b, cx_b),
 893        RoomParticipants {
 894            remote: Default::default(),
 895            pending: Default::default()
 896        }
 897    );
 898}
 899
 900#[gpui::test(iterations = 10)]
 901async fn test_server_restarts(
 902    executor: BackgroundExecutor,
 903    cx_a: &mut TestAppContext,
 904    cx_b: &mut TestAppContext,
 905    cx_c: &mut TestAppContext,
 906    cx_d: &mut TestAppContext,
 907) {
 908    let mut server = TestServer::start(executor.clone()).await;
 909    let client_a = server.create_client(cx_a, "user_a").await;
 910    client_a
 911        .fs()
 912        .insert_tree("/a", json!({ "a.txt": "a-contents" }))
 913        .await;
 914
 915    // Invite client B to collaborate on a project
 916    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
 917
 918    let client_b = server.create_client(cx_b, "user_b").await;
 919    let client_c = server.create_client(cx_c, "user_c").await;
 920    let client_d = server.create_client(cx_d, "user_d").await;
 921    server
 922        .make_contacts(&mut [
 923            (&client_a, cx_a),
 924            (&client_b, cx_b),
 925            (&client_c, cx_c),
 926            (&client_d, cx_d),
 927        ])
 928        .await;
 929
 930    let active_call_a = cx_a.read(ActiveCall::global);
 931    let active_call_b = cx_b.read(ActiveCall::global);
 932    let active_call_c = cx_c.read(ActiveCall::global);
 933    let active_call_d = cx_d.read(ActiveCall::global);
 934
 935    // User A calls users B, C, and D.
 936    active_call_a
 937        .update(cx_a, |call, cx| {
 938            call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 939        })
 940        .await
 941        .unwrap();
 942    active_call_a
 943        .update(cx_a, |call, cx| {
 944            call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
 945        })
 946        .await
 947        .unwrap();
 948    active_call_a
 949        .update(cx_a, |call, cx| {
 950            call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
 951        })
 952        .await
 953        .unwrap();
 954
 955    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
 956
 957    // User B receives the call and joins the room.
 958
 959    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 960    assert!(incoming_call_b.next().await.unwrap().is_some());
 961    active_call_b
 962        .update(cx_b, |call, cx| call.accept_incoming(cx))
 963        .await
 964        .unwrap();
 965
 966    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
 967
 968    // User C receives the call and joins the room.
 969
 970    let mut incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
 971    assert!(incoming_call_c.next().await.unwrap().is_some());
 972    active_call_c
 973        .update(cx_c, |call, cx| call.accept_incoming(cx))
 974        .await
 975        .unwrap();
 976
 977    let room_c = active_call_c.read_with(cx_c, |call, _| call.room().unwrap().clone());
 978
 979    // User D receives the call but doesn't join the room yet.
 980
 981    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
 982    assert!(incoming_call_d.next().await.unwrap().is_some());
 983
 984    executor.run_until_parked();
 985    assert_eq!(
 986        room_participants(&room_a, cx_a),
 987        RoomParticipants {
 988            remote: vec!["user_b".to_string(), "user_c".to_string()],
 989            pending: vec!["user_d".to_string()]
 990        }
 991    );
 992    assert_eq!(
 993        room_participants(&room_b, cx_b),
 994        RoomParticipants {
 995            remote: vec!["user_a".to_string(), "user_c".to_string()],
 996            pending: vec!["user_d".to_string()]
 997        }
 998    );
 999    assert_eq!(
1000        room_participants(&room_c, cx_c),
1001        RoomParticipants {
1002            remote: vec!["user_a".to_string(), "user_b".to_string()],
1003            pending: vec!["user_d".to_string()]
1004        }
1005    );
1006
1007    // The server is torn down.
1008    server.reset().await;
1009
1010    // Users A and B reconnect to the call. User C has troubles reconnecting, so it leaves the room.
1011    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1012    executor.advance_clock(RECONNECT_TIMEOUT);
1013    assert_eq!(
1014        room_participants(&room_a, cx_a),
1015        RoomParticipants {
1016            remote: vec!["user_b".to_string(), "user_c".to_string()],
1017            pending: vec!["user_d".to_string()]
1018        }
1019    );
1020    assert_eq!(
1021        room_participants(&room_b, cx_b),
1022        RoomParticipants {
1023            remote: vec!["user_a".to_string(), "user_c".to_string()],
1024            pending: vec!["user_d".to_string()]
1025        }
1026    );
1027    assert_eq!(
1028        room_participants(&room_c, cx_c),
1029        RoomParticipants {
1030            remote: vec![],
1031            pending: vec![]
1032        }
1033    );
1034
1035    // User D is notified again of the incoming call and accepts it.
1036    assert!(incoming_call_d.next().await.unwrap().is_some());
1037    active_call_d
1038        .update(cx_d, |call, cx| call.accept_incoming(cx))
1039        .await
1040        .unwrap();
1041    executor.run_until_parked();
1042
1043    let room_d = active_call_d.read_with(cx_d, |call, _| call.room().unwrap().clone());
1044    assert_eq!(
1045        room_participants(&room_a, cx_a),
1046        RoomParticipants {
1047            remote: vec![
1048                "user_b".to_string(),
1049                "user_c".to_string(),
1050                "user_d".to_string(),
1051            ],
1052            pending: vec![]
1053        }
1054    );
1055    assert_eq!(
1056        room_participants(&room_b, cx_b),
1057        RoomParticipants {
1058            remote: vec![
1059                "user_a".to_string(),
1060                "user_c".to_string(),
1061                "user_d".to_string(),
1062            ],
1063            pending: vec![]
1064        }
1065    );
1066    assert_eq!(
1067        room_participants(&room_c, cx_c),
1068        RoomParticipants {
1069            remote: vec![],
1070            pending: vec![]
1071        }
1072    );
1073    assert_eq!(
1074        room_participants(&room_d, cx_d),
1075        RoomParticipants {
1076            remote: vec![
1077                "user_a".to_string(),
1078                "user_b".to_string(),
1079                "user_c".to_string(),
1080            ],
1081            pending: vec![]
1082        }
1083    );
1084
1085    // The server finishes restarting, cleaning up stale connections.
1086    server.start().await.unwrap();
1087    executor.advance_clock(CLEANUP_TIMEOUT);
1088    assert_eq!(
1089        room_participants(&room_a, cx_a),
1090        RoomParticipants {
1091            remote: vec!["user_b".to_string(), "user_d".to_string()],
1092            pending: vec![]
1093        }
1094    );
1095    assert_eq!(
1096        room_participants(&room_b, cx_b),
1097        RoomParticipants {
1098            remote: vec!["user_a".to_string(), "user_d".to_string()],
1099            pending: vec![]
1100        }
1101    );
1102    assert_eq!(
1103        room_participants(&room_c, cx_c),
1104        RoomParticipants {
1105            remote: vec![],
1106            pending: vec![]
1107        }
1108    );
1109    assert_eq!(
1110        room_participants(&room_d, cx_d),
1111        RoomParticipants {
1112            remote: vec!["user_a".to_string(), "user_b".to_string()],
1113            pending: vec![]
1114        }
1115    );
1116
1117    // User D hangs up.
1118    active_call_d
1119        .update(cx_d, |call, cx| call.hang_up(cx))
1120        .await
1121        .unwrap();
1122    executor.run_until_parked();
1123    assert_eq!(
1124        room_participants(&room_a, cx_a),
1125        RoomParticipants {
1126            remote: vec!["user_b".to_string()],
1127            pending: vec![]
1128        }
1129    );
1130    assert_eq!(
1131        room_participants(&room_b, cx_b),
1132        RoomParticipants {
1133            remote: vec!["user_a".to_string()],
1134            pending: vec![]
1135        }
1136    );
1137    assert_eq!(
1138        room_participants(&room_c, cx_c),
1139        RoomParticipants {
1140            remote: vec![],
1141            pending: vec![]
1142        }
1143    );
1144    assert_eq!(
1145        room_participants(&room_d, cx_d),
1146        RoomParticipants {
1147            remote: vec![],
1148            pending: vec![]
1149        }
1150    );
1151
1152    // User B calls user D again.
1153    active_call_b
1154        .update(cx_b, |call, cx| {
1155            call.invite(client_d.user_id().unwrap(), None, cx)
1156        })
1157        .await
1158        .unwrap();
1159
1160    // User D receives the call but doesn't join the room yet.
1161
1162    let mut incoming_call_d = active_call_d.read_with(cx_d, |call, _| call.incoming());
1163    assert!(incoming_call_d.next().await.unwrap().is_some());
1164    executor.run_until_parked();
1165    assert_eq!(
1166        room_participants(&room_a, cx_a),
1167        RoomParticipants {
1168            remote: vec!["user_b".to_string()],
1169            pending: vec!["user_d".to_string()]
1170        }
1171    );
1172    assert_eq!(
1173        room_participants(&room_b, cx_b),
1174        RoomParticipants {
1175            remote: vec!["user_a".to_string()],
1176            pending: vec!["user_d".to_string()]
1177        }
1178    );
1179
1180    // The server is torn down.
1181    server.reset().await;
1182
1183    // Users A and B have troubles reconnecting, so they leave the room.
1184    client_a.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1185    client_b.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1186    client_c.override_establish_connection(|_, cx| cx.spawn(async |_| future::pending().await));
1187    executor.advance_clock(RECONNECT_TIMEOUT);
1188    assert_eq!(
1189        room_participants(&room_a, cx_a),
1190        RoomParticipants {
1191            remote: vec![],
1192            pending: vec![]
1193        }
1194    );
1195    assert_eq!(
1196        room_participants(&room_b, cx_b),
1197        RoomParticipants {
1198            remote: vec![],
1199            pending: vec![]
1200        }
1201    );
1202
1203    // User D is notified again of the incoming call but doesn't accept it.
1204    assert!(incoming_call_d.next().await.unwrap().is_some());
1205
1206    // The server finishes restarting, cleaning up stale connections and canceling the
1207    // call to user D because the room has become empty.
1208    server.start().await.unwrap();
1209    executor.advance_clock(CLEANUP_TIMEOUT);
1210    assert!(incoming_call_d.next().await.unwrap().is_none());
1211}
1212
1213#[gpui::test(iterations = 10)]
1214async fn test_calls_on_multiple_connections(
1215    executor: BackgroundExecutor,
1216    cx_a: &mut TestAppContext,
1217    cx_b1: &mut TestAppContext,
1218    cx_b2: &mut TestAppContext,
1219) {
1220    let mut server = TestServer::start(executor.clone()).await;
1221    let client_a = server.create_client(cx_a, "user_a").await;
1222    let client_b1 = server.create_client(cx_b1, "user_b").await;
1223    let client_b2 = server.create_client(cx_b2, "user_b").await;
1224    server
1225        .make_contacts(&mut [(&client_a, cx_a), (&client_b1, cx_b1)])
1226        .await;
1227
1228    let active_call_a = cx_a.read(ActiveCall::global);
1229    let active_call_b1 = cx_b1.read(ActiveCall::global);
1230    let active_call_b2 = cx_b2.read(ActiveCall::global);
1231
1232    let mut incoming_call_b1 = active_call_b1.read_with(cx_b1, |call, _| call.incoming());
1233
1234    let mut incoming_call_b2 = active_call_b2.read_with(cx_b2, |call, _| call.incoming());
1235    assert!(incoming_call_b1.next().await.unwrap().is_none());
1236    assert!(incoming_call_b2.next().await.unwrap().is_none());
1237
1238    // Call user B from client A, ensuring both clients for user B ring.
1239    active_call_a
1240        .update(cx_a, |call, cx| {
1241            call.invite(client_b1.user_id().unwrap(), None, cx)
1242        })
1243        .await
1244        .unwrap();
1245    executor.run_until_parked();
1246    assert!(incoming_call_b1.next().await.unwrap().is_some());
1247    assert!(incoming_call_b2.next().await.unwrap().is_some());
1248
1249    // User B declines the call on one of the two connections, causing both connections
1250    // to stop ringing.
1251    active_call_b2.update(cx_b2, |call, cx| call.decline_incoming(cx).unwrap());
1252    executor.run_until_parked();
1253    assert!(incoming_call_b1.next().await.unwrap().is_none());
1254    assert!(incoming_call_b2.next().await.unwrap().is_none());
1255
1256    // Call user B again from client A.
1257    active_call_a
1258        .update(cx_a, |call, cx| {
1259            call.invite(client_b1.user_id().unwrap(), None, cx)
1260        })
1261        .await
1262        .unwrap();
1263    executor.run_until_parked();
1264    assert!(incoming_call_b1.next().await.unwrap().is_some());
1265    assert!(incoming_call_b2.next().await.unwrap().is_some());
1266
1267    // User B accepts the call on one of the two connections, causing both connections
1268    // to stop ringing.
1269    active_call_b2
1270        .update(cx_b2, |call, cx| call.accept_incoming(cx))
1271        .await
1272        .unwrap();
1273    executor.run_until_parked();
1274    assert!(incoming_call_b1.next().await.unwrap().is_none());
1275    assert!(incoming_call_b2.next().await.unwrap().is_none());
1276
1277    // User B disconnects the client that is not on the call. Everything should be fine.
1278    client_b1.disconnect(&cx_b1.to_async());
1279    executor.advance_clock(RECEIVE_TIMEOUT);
1280    client_b1
1281        .authenticate_and_connect(false, &cx_b1.to_async())
1282        .await
1283        .into_response()
1284        .unwrap();
1285
1286    // User B hangs up, and user A calls them again.
1287    active_call_b2
1288        .update(cx_b2, |call, cx| call.hang_up(cx))
1289        .await
1290        .unwrap();
1291    executor.run_until_parked();
1292    active_call_a
1293        .update(cx_a, |call, cx| {
1294            call.invite(client_b1.user_id().unwrap(), None, cx)
1295        })
1296        .await
1297        .unwrap();
1298    executor.run_until_parked();
1299    assert!(incoming_call_b1.next().await.unwrap().is_some());
1300    assert!(incoming_call_b2.next().await.unwrap().is_some());
1301
1302    // User A cancels the call, causing both connections to stop ringing.
1303    active_call_a
1304        .update(cx_a, |call, cx| {
1305            call.cancel_invite(client_b1.user_id().unwrap(), cx)
1306        })
1307        .await
1308        .unwrap();
1309    executor.run_until_parked();
1310    assert!(incoming_call_b1.next().await.unwrap().is_none());
1311    assert!(incoming_call_b2.next().await.unwrap().is_none());
1312
1313    // User A calls user B again.
1314    active_call_a
1315        .update(cx_a, |call, cx| {
1316            call.invite(client_b1.user_id().unwrap(), None, cx)
1317        })
1318        .await
1319        .unwrap();
1320    executor.run_until_parked();
1321    assert!(incoming_call_b1.next().await.unwrap().is_some());
1322    assert!(incoming_call_b2.next().await.unwrap().is_some());
1323
1324    // User A hangs up, causing both connections to stop ringing.
1325    active_call_a
1326        .update(cx_a, |call, cx| call.hang_up(cx))
1327        .await
1328        .unwrap();
1329    executor.run_until_parked();
1330    assert!(incoming_call_b1.next().await.unwrap().is_none());
1331    assert!(incoming_call_b2.next().await.unwrap().is_none());
1332
1333    // User A calls user B again.
1334    active_call_a
1335        .update(cx_a, |call, cx| {
1336            call.invite(client_b1.user_id().unwrap(), None, cx)
1337        })
1338        .await
1339        .unwrap();
1340    executor.run_until_parked();
1341    assert!(incoming_call_b1.next().await.unwrap().is_some());
1342    assert!(incoming_call_b2.next().await.unwrap().is_some());
1343
1344    // User A disconnects, causing both connections to stop ringing.
1345    server.forbid_connections();
1346    server.disconnect_client(client_a.peer_id().unwrap());
1347    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1348    assert!(incoming_call_b1.next().await.unwrap().is_none());
1349    assert!(incoming_call_b2.next().await.unwrap().is_none());
1350
1351    // User A reconnects automatically, then calls user B again.
1352    server.allow_connections();
1353    executor.advance_clock(RECEIVE_TIMEOUT);
1354    active_call_a
1355        .update(cx_a, |call, cx| {
1356            call.invite(client_b1.user_id().unwrap(), None, cx)
1357        })
1358        .await
1359        .unwrap();
1360    executor.run_until_parked();
1361    assert!(incoming_call_b1.next().await.unwrap().is_some());
1362    assert!(incoming_call_b2.next().await.unwrap().is_some());
1363
1364    // User B disconnects all clients, causing user A to no longer see a pending call for them.
1365    server.forbid_connections();
1366    server.disconnect_client(client_b1.peer_id().unwrap());
1367    server.disconnect_client(client_b2.peer_id().unwrap());
1368    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
1369
1370    active_call_a.read_with(cx_a, |call, _| assert!(call.room().is_none()));
1371}
1372
1373#[gpui::test(iterations = 10)]
1374async fn test_unshare_project(
1375    executor: BackgroundExecutor,
1376    cx_a: &mut TestAppContext,
1377    cx_b: &mut TestAppContext,
1378    cx_c: &mut TestAppContext,
1379) {
1380    let mut server = TestServer::start(executor.clone()).await;
1381    let client_a = server.create_client(cx_a, "user_a").await;
1382    let client_b = server.create_client(cx_b, "user_b").await;
1383    let client_c = server.create_client(cx_c, "user_c").await;
1384    server
1385        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1386        .await;
1387
1388    let active_call_a = cx_a.read(ActiveCall::global);
1389    let active_call_b = cx_b.read(ActiveCall::global);
1390
1391    client_a
1392        .fs()
1393        .insert_tree(
1394            "/a",
1395            json!({
1396                "a.txt": "a-contents",
1397                "b.txt": "b-contents",
1398            }),
1399        )
1400        .await;
1401
1402    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1403    let project_id = active_call_a
1404        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1405        .await
1406        .unwrap();
1407
1408    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1409    let project_b = client_b.join_remote_project(project_id, cx_b).await;
1410    executor.run_until_parked();
1411
1412    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1413
1414    project_b
1415        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1416        .await
1417        .unwrap();
1418
1419    // When client B leaves the room, the project becomes read-only.
1420    active_call_b
1421        .update(cx_b, |call, cx| call.hang_up(cx))
1422        .await
1423        .unwrap();
1424    executor.run_until_parked();
1425
1426    assert!(project_b.read_with(cx_b, |project, cx| project.is_disconnected(cx)));
1427
1428    // Client C opens the project.
1429    let project_c = client_c.join_remote_project(project_id, cx_c).await;
1430
1431    // When client A unshares the project, client C's project becomes read-only.
1432    project_a
1433        .update(cx_a, |project, cx| project.unshare(cx))
1434        .unwrap();
1435    executor.run_until_parked();
1436
1437    assert!(worktree_a.read_with(cx_a, |tree, _| !tree.has_update_observer()));
1438
1439    assert!(project_c.read_with(cx_c, |project, cx| project.is_disconnected(cx)));
1440
1441    // Client C can open the project again after client A re-shares.
1442    let project_id = active_call_a
1443        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1444        .await
1445        .unwrap();
1446    let project_c2 = client_c.join_remote_project(project_id, cx_c).await;
1447    executor.run_until_parked();
1448
1449    assert!(worktree_a.read_with(cx_a, |tree, _| tree.has_update_observer()));
1450    project_c2
1451        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
1452        .await
1453        .unwrap();
1454
1455    // When client A (the host) leaves the room, the project gets unshared and guests are notified.
1456    active_call_a
1457        .update(cx_a, |call, cx| call.hang_up(cx))
1458        .await
1459        .unwrap();
1460    executor.run_until_parked();
1461
1462    project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
1463
1464    project_c2.read_with(cx_c, |project, cx| {
1465        assert!(project.is_disconnected(cx));
1466        assert!(project.collaborators().is_empty());
1467    });
1468}
1469
1470#[gpui::test(iterations = 10)]
1471async fn test_project_reconnect(
1472    executor: BackgroundExecutor,
1473    cx_a: &mut TestAppContext,
1474    cx_b: &mut TestAppContext,
1475) {
1476    let mut server = TestServer::start(executor.clone()).await;
1477    let client_a = server.create_client(cx_a, "user_a").await;
1478    let client_b = server.create_client(cx_b, "user_b").await;
1479    server
1480        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1481        .await;
1482
1483    cx_b.update(editor::init);
1484
1485    client_a
1486        .fs()
1487        .insert_tree(
1488            path!("/root-1"),
1489            json!({
1490                "dir1": {
1491                    "a.txt": "a",
1492                    "b.txt": "b",
1493                    "subdir1": {
1494                        "c.txt": "c",
1495                        "d.txt": "d",
1496                        "e.txt": "e",
1497                    }
1498                },
1499                "dir2": {
1500                    "v.txt": "v",
1501                },
1502                "dir3": {
1503                    "w.txt": "w",
1504                    "x.txt": "x",
1505                    "y.txt": "y",
1506                },
1507                "dir4": {
1508                    "z.txt": "z",
1509                },
1510            }),
1511        )
1512        .await;
1513    client_a
1514        .fs()
1515        .insert_tree(
1516            path!("/root-2"),
1517            json!({
1518                "2.txt": "2",
1519            }),
1520        )
1521        .await;
1522    client_a
1523        .fs()
1524        .insert_tree(
1525            path!("/root-3"),
1526            json!({
1527                "3.txt": "3",
1528            }),
1529        )
1530        .await;
1531
1532    let active_call_a = cx_a.read(ActiveCall::global);
1533    let (project_a1, _) = client_a
1534        .build_local_project(path!("/root-1/dir1"), cx_a)
1535        .await;
1536    let (project_a2, _) = client_a.build_local_project(path!("/root-2"), cx_a).await;
1537    let (project_a3, _) = client_a.build_local_project(path!("/root-3"), cx_a).await;
1538    let worktree_a1 =
1539        project_a1.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
1540    let project1_id = active_call_a
1541        .update(cx_a, |call, cx| call.share_project(project_a1.clone(), cx))
1542        .await
1543        .unwrap();
1544    let project2_id = active_call_a
1545        .update(cx_a, |call, cx| call.share_project(project_a2.clone(), cx))
1546        .await
1547        .unwrap();
1548    let project3_id = active_call_a
1549        .update(cx_a, |call, cx| call.share_project(project_a3.clone(), cx))
1550        .await
1551        .unwrap();
1552
1553    let project_b1 = client_b.join_remote_project(project1_id, cx_b).await;
1554    let project_b2 = client_b.join_remote_project(project2_id, cx_b).await;
1555    let project_b3 = client_b.join_remote_project(project3_id, cx_b).await;
1556    executor.run_until_parked();
1557
1558    let worktree1_id = worktree_a1.read_with(cx_a, |worktree, _| {
1559        assert!(worktree.has_update_observer());
1560        worktree.id()
1561    });
1562    let (worktree_a2, _) = project_a1
1563        .update(cx_a, |p, cx| {
1564            p.find_or_create_worktree(path!("/root-1/dir2"), true, cx)
1565        })
1566        .await
1567        .unwrap();
1568    executor.run_until_parked();
1569
1570    let worktree2_id = worktree_a2.read_with(cx_a, |tree, _| {
1571        assert!(tree.has_update_observer());
1572        tree.id()
1573    });
1574    executor.run_until_parked();
1575
1576    project_b1.read_with(cx_b, |project, cx| {
1577        assert!(project.worktree_for_id(worktree2_id, cx).is_some())
1578    });
1579
1580    let buffer_a1 = project_a1
1581        .update(cx_a, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1582        .await
1583        .unwrap();
1584    let buffer_b1 = project_b1
1585        .update(cx_b, |p, cx| p.open_buffer((worktree1_id, "a.txt"), cx))
1586        .await
1587        .unwrap();
1588
1589    // Drop client A's connection.
1590    server.forbid_connections();
1591    server.disconnect_client(client_a.peer_id().unwrap());
1592    executor.advance_clock(RECEIVE_TIMEOUT);
1593
1594    project_a1.read_with(cx_a, |project, _| {
1595        assert!(project.is_shared());
1596        assert_eq!(project.collaborators().len(), 1);
1597    });
1598
1599    project_b1.read_with(cx_b, |project, cx| {
1600        assert!(!project.is_disconnected(cx));
1601        assert_eq!(project.collaborators().len(), 1);
1602    });
1603
1604    worktree_a1.read_with(cx_a, |tree, _| assert!(tree.has_update_observer()));
1605
1606    // While client A is disconnected, add and remove files from client A's project.
1607    client_a
1608        .fs()
1609        .insert_tree(
1610            path!("/root-1/dir1/subdir2"),
1611            json!({
1612                "f.txt": "f-contents",
1613                "g.txt": "g-contents",
1614                "h.txt": "h-contents",
1615                "i.txt": "i-contents",
1616            }),
1617        )
1618        .await;
1619    client_a
1620        .fs()
1621        .remove_dir(
1622            path!("/root-1/dir1/subdir1").as_ref(),
1623            RemoveOptions {
1624                recursive: true,
1625                ..Default::default()
1626            },
1627        )
1628        .await
1629        .unwrap();
1630
1631    // While client A is disconnected, add and remove worktrees from client A's project.
1632    project_a1.update(cx_a, |project, cx| {
1633        project.remove_worktree(worktree2_id, cx)
1634    });
1635    let (worktree_a3, _) = project_a1
1636        .update(cx_a, |p, cx| {
1637            p.find_or_create_worktree(path!("/root-1/dir3"), true, cx)
1638        })
1639        .await
1640        .unwrap();
1641    worktree_a3
1642        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
1643        .await;
1644
1645    let worktree3_id = worktree_a3.read_with(cx_a, |tree, _| {
1646        assert!(!tree.has_update_observer());
1647        tree.id()
1648    });
1649    executor.run_until_parked();
1650
1651    // While client A is disconnected, close project 2
1652    cx_a.update(|_| drop(project_a2));
1653
1654    // While client A is disconnected, mutate a buffer on both the host and the guest.
1655    buffer_a1.update(cx_a, |buf, cx| buf.edit([(0..0, "W")], None, cx));
1656    buffer_b1.update(cx_b, |buf, cx| buf.edit([(1..1, "Z")], None, cx));
1657    executor.run_until_parked();
1658
1659    // Client A reconnects. Their project is re-shared, and client B re-joins it.
1660    server.allow_connections();
1661    client_a
1662        .authenticate_and_connect(false, &cx_a.to_async())
1663        .await
1664        .into_response()
1665        .unwrap();
1666    executor.run_until_parked();
1667
1668    project_a1.read_with(cx_a, |project, cx| {
1669        assert!(project.is_shared());
1670        assert!(worktree_a1.read(cx).has_update_observer());
1671        assert_eq!(
1672            worktree_a1
1673                .read(cx)
1674                .snapshot()
1675                .paths()
1676                .map(|p| p.to_str().unwrap())
1677                .collect::<Vec<_>>(),
1678            vec![
1679                path!("a.txt"),
1680                path!("b.txt"),
1681                path!("subdir2"),
1682                path!("subdir2/f.txt"),
1683                path!("subdir2/g.txt"),
1684                path!("subdir2/h.txt"),
1685                path!("subdir2/i.txt")
1686            ]
1687        );
1688        assert!(worktree_a3.read(cx).has_update_observer());
1689        assert_eq!(
1690            worktree_a3
1691                .read(cx)
1692                .snapshot()
1693                .paths()
1694                .map(|p| p.to_str().unwrap())
1695                .collect::<Vec<_>>(),
1696            vec!["w.txt", "x.txt", "y.txt"]
1697        );
1698    });
1699
1700    project_b1.read_with(cx_b, |project, cx| {
1701        assert!(!project.is_disconnected(cx));
1702        assert_eq!(
1703            project
1704                .worktree_for_id(worktree1_id, cx)
1705                .unwrap()
1706                .read(cx)
1707                .snapshot()
1708                .paths()
1709                .map(|p| p.to_str().unwrap())
1710                .collect::<Vec<_>>(),
1711            vec![
1712                path!("a.txt"),
1713                path!("b.txt"),
1714                path!("subdir2"),
1715                path!("subdir2/f.txt"),
1716                path!("subdir2/g.txt"),
1717                path!("subdir2/h.txt"),
1718                path!("subdir2/i.txt")
1719            ]
1720        );
1721        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1722        assert_eq!(
1723            project
1724                .worktree_for_id(worktree3_id, cx)
1725                .unwrap()
1726                .read(cx)
1727                .snapshot()
1728                .paths()
1729                .map(|p| p.to_str().unwrap())
1730                .collect::<Vec<_>>(),
1731            vec!["w.txt", "x.txt", "y.txt"]
1732        );
1733    });
1734
1735    project_b2.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1736
1737    project_b3.read_with(cx_b, |project, cx| assert!(!project.is_disconnected(cx)));
1738
1739    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1740
1741    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WaZ"));
1742
1743    // Drop client B's connection.
1744    server.forbid_connections();
1745    server.disconnect_client(client_b.peer_id().unwrap());
1746    executor.advance_clock(RECEIVE_TIMEOUT);
1747
1748    // While client B is disconnected, add and remove files from client A's project
1749    client_a
1750        .fs()
1751        .insert_file(path!("/root-1/dir1/subdir2/j.txt"), "j-contents".into())
1752        .await;
1753    client_a
1754        .fs()
1755        .remove_file(
1756            path!("/root-1/dir1/subdir2/i.txt").as_ref(),
1757            Default::default(),
1758        )
1759        .await
1760        .unwrap();
1761
1762    // While client B is disconnected, add and remove worktrees from client A's project.
1763    let (worktree_a4, _) = project_a1
1764        .update(cx_a, |p, cx| {
1765            p.find_or_create_worktree(path!("/root-1/dir4"), true, cx)
1766        })
1767        .await
1768        .unwrap();
1769    executor.run_until_parked();
1770
1771    let worktree4_id = worktree_a4.read_with(cx_a, |tree, _| {
1772        assert!(tree.has_update_observer());
1773        tree.id()
1774    });
1775    project_a1.update(cx_a, |project, cx| {
1776        project.remove_worktree(worktree3_id, cx)
1777    });
1778    executor.run_until_parked();
1779
1780    // While client B is disconnected, mutate a buffer on both the host and the guest.
1781    buffer_a1.update(cx_a, |buf, cx| buf.edit([(1..1, "X")], None, cx));
1782    buffer_b1.update(cx_b, |buf, cx| buf.edit([(2..2, "Y")], None, cx));
1783    executor.run_until_parked();
1784
1785    // While disconnected, close project 3
1786    cx_a.update(|_| drop(project_a3));
1787
1788    // Client B reconnects. They re-join the room and the remaining shared project.
1789    server.allow_connections();
1790    client_b
1791        .authenticate_and_connect(false, &cx_b.to_async())
1792        .await
1793        .into_response()
1794        .unwrap();
1795    executor.run_until_parked();
1796
1797    project_b1.read_with(cx_b, |project, cx| {
1798        assert!(!project.is_disconnected(cx));
1799        assert_eq!(
1800            project
1801                .worktree_for_id(worktree1_id, cx)
1802                .unwrap()
1803                .read(cx)
1804                .snapshot()
1805                .paths()
1806                .map(|p| p.to_str().unwrap())
1807                .collect::<Vec<_>>(),
1808            vec![
1809                path!("a.txt"),
1810                path!("b.txt"),
1811                path!("subdir2"),
1812                path!("subdir2/f.txt"),
1813                path!("subdir2/g.txt"),
1814                path!("subdir2/h.txt"),
1815                path!("subdir2/j.txt")
1816            ]
1817        );
1818        assert!(project.worktree_for_id(worktree2_id, cx).is_none());
1819        assert_eq!(
1820            project
1821                .worktree_for_id(worktree4_id, cx)
1822                .unwrap()
1823                .read(cx)
1824                .snapshot()
1825                .paths()
1826                .map(|p| p.to_str().unwrap())
1827                .collect::<Vec<_>>(),
1828            vec!["z.txt"]
1829        );
1830    });
1831
1832    project_b3.read_with(cx_b, |project, cx| assert!(project.is_disconnected(cx)));
1833
1834    buffer_a1.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1835
1836    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "WXaYZ"));
1837}
1838
1839#[gpui::test(iterations = 10)]
1840async fn test_active_call_events(
1841    executor: BackgroundExecutor,
1842    cx_a: &mut TestAppContext,
1843    cx_b: &mut TestAppContext,
1844) {
1845    let mut server = TestServer::start(executor.clone()).await;
1846    let client_a = server.create_client(cx_a, "user_a").await;
1847    let client_b = server.create_client(cx_b, "user_b").await;
1848    client_a.fs().insert_tree("/a", json!({})).await;
1849    client_b.fs().insert_tree("/b", json!({})).await;
1850
1851    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1852    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
1853
1854    server
1855        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1856        .await;
1857    executor.run_until_parked();
1858
1859    let active_call_a = cx_a.read(ActiveCall::global);
1860    let active_call_b = cx_b.read(ActiveCall::global);
1861
1862    let events_a = active_call_events(cx_a);
1863    let events_b = active_call_events(cx_b);
1864
1865    let project_a_id = active_call_a
1866        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1867        .await
1868        .unwrap();
1869    executor.run_until_parked();
1870    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1871    assert_eq!(
1872        mem::take(&mut *events_b.borrow_mut()),
1873        vec![room::Event::RemoteProjectShared {
1874            owner: Arc::new(User {
1875                id: client_a.user_id().unwrap(),
1876                github_login: "user_a".to_string(),
1877                avatar_uri: "avatar_a".into(),
1878                name: None,
1879            }),
1880            project_id: project_a_id,
1881            worktree_root_names: vec!["a".to_string()],
1882        }]
1883    );
1884
1885    let project_b_id = active_call_b
1886        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1887        .await
1888        .unwrap();
1889    executor.run_until_parked();
1890    assert_eq!(
1891        mem::take(&mut *events_a.borrow_mut()),
1892        vec![room::Event::RemoteProjectShared {
1893            owner: Arc::new(User {
1894                id: client_b.user_id().unwrap(),
1895                github_login: "user_b".to_string(),
1896                avatar_uri: "avatar_b".into(),
1897                name: None,
1898            }),
1899            project_id: project_b_id,
1900            worktree_root_names: vec!["b".to_string()]
1901        }]
1902    );
1903    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1904
1905    // Sharing a project twice is idempotent.
1906    let project_b_id_2 = active_call_b
1907        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1908        .await
1909        .unwrap();
1910    assert_eq!(project_b_id_2, project_b_id);
1911    executor.run_until_parked();
1912    assert_eq!(mem::take(&mut *events_a.borrow_mut()), vec![]);
1913    assert_eq!(mem::take(&mut *events_b.borrow_mut()), vec![]);
1914
1915    // Unsharing a project should dispatch the RemoteProjectUnshared event.
1916    active_call_a
1917        .update(cx_a, |call, cx| call.hang_up(cx))
1918        .await
1919        .unwrap();
1920    executor.run_until_parked();
1921
1922    assert_eq!(
1923        mem::take(&mut *events_a.borrow_mut()),
1924        vec![room::Event::RoomLeft { channel_id: None }]
1925    );
1926    assert_eq!(
1927        mem::take(&mut *events_b.borrow_mut()),
1928        vec![room::Event::RemoteProjectUnshared {
1929            project_id: project_a_id,
1930        }]
1931    );
1932}
1933
1934fn active_call_events(cx: &mut TestAppContext) -> Rc<RefCell<Vec<room::Event>>> {
1935    let events = Rc::new(RefCell::new(Vec::new()));
1936    let active_call = cx.read(ActiveCall::global);
1937    cx.update({
1938        let events = events.clone();
1939        |cx| {
1940            cx.subscribe(&active_call, move |_, event, _| {
1941                events.borrow_mut().push(event.clone())
1942            })
1943            .detach()
1944        }
1945    });
1946    events
1947}
1948
1949#[gpui::test]
1950async fn test_mute_deafen(
1951    executor: BackgroundExecutor,
1952    cx_a: &mut TestAppContext,
1953    cx_b: &mut TestAppContext,
1954    cx_c: &mut TestAppContext,
1955) {
1956    let mut server = TestServer::start(executor.clone()).await;
1957    let client_a = server.create_client(cx_a, "user_a").await;
1958    let client_b = server.create_client(cx_b, "user_b").await;
1959    let client_c = server.create_client(cx_c, "user_c").await;
1960
1961    server
1962        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
1963        .await;
1964
1965    let active_call_a = cx_a.read(ActiveCall::global);
1966    let active_call_b = cx_b.read(ActiveCall::global);
1967    let active_call_c = cx_c.read(ActiveCall::global);
1968
1969    // User A calls user B, B answers.
1970    active_call_a
1971        .update(cx_a, |call, cx| {
1972            call.invite(client_b.user_id().unwrap(), None, cx)
1973        })
1974        .await
1975        .unwrap();
1976    executor.run_until_parked();
1977    active_call_b
1978        .update(cx_b, |call, cx| call.accept_incoming(cx))
1979        .await
1980        .unwrap();
1981    executor.run_until_parked();
1982
1983    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
1984    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
1985
1986    room_a.read_with(cx_a, |room, _| assert!(!room.is_muted()));
1987    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
1988
1989    // Users A and B are both unmuted.
1990    assert_eq!(
1991        participant_audio_state(&room_a, cx_a),
1992        &[ParticipantAudioState {
1993            user_id: client_b.user_id().unwrap(),
1994            is_muted: false,
1995            audio_tracks_playing: vec![true],
1996        }]
1997    );
1998    assert_eq!(
1999        participant_audio_state(&room_b, cx_b),
2000        &[ParticipantAudioState {
2001            user_id: client_a.user_id().unwrap(),
2002            is_muted: false,
2003            audio_tracks_playing: vec![true],
2004        }]
2005    );
2006
2007    // User A mutes
2008    room_a.update(cx_a, |room, cx| room.toggle_mute(cx));
2009    executor.run_until_parked();
2010
2011    // User A hears user B, but B doesn't hear A.
2012    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2013    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2014    assert_eq!(
2015        participant_audio_state(&room_a, cx_a),
2016        &[ParticipantAudioState {
2017            user_id: client_b.user_id().unwrap(),
2018            is_muted: false,
2019            audio_tracks_playing: vec![true],
2020        }]
2021    );
2022    assert_eq!(
2023        participant_audio_state(&room_b, cx_b),
2024        &[ParticipantAudioState {
2025            user_id: client_a.user_id().unwrap(),
2026            is_muted: true,
2027            audio_tracks_playing: vec![true],
2028        }]
2029    );
2030
2031    // User A deafens
2032    room_a.update(cx_a, |room, cx| room.toggle_deafen(cx));
2033    executor.run_until_parked();
2034
2035    // User A does not hear user B.
2036    room_a.read_with(cx_a, |room, _| assert!(room.is_muted()));
2037    room_b.read_with(cx_b, |room, _| assert!(!room.is_muted()));
2038    assert_eq!(
2039        participant_audio_state(&room_a, cx_a),
2040        &[ParticipantAudioState {
2041            user_id: client_b.user_id().unwrap(),
2042            is_muted: false,
2043            audio_tracks_playing: vec![false],
2044        }]
2045    );
2046    assert_eq!(
2047        participant_audio_state(&room_b, cx_b),
2048        &[ParticipantAudioState {
2049            user_id: client_a.user_id().unwrap(),
2050            is_muted: true,
2051            audio_tracks_playing: vec![true],
2052        }]
2053    );
2054
2055    // User B calls user C, C joins.
2056    active_call_b
2057        .update(cx_b, |call, cx| {
2058            call.invite(client_c.user_id().unwrap(), None, cx)
2059        })
2060        .await
2061        .unwrap();
2062    executor.run_until_parked();
2063    active_call_c
2064        .update(cx_c, |call, cx| call.accept_incoming(cx))
2065        .await
2066        .unwrap();
2067    executor.run_until_parked();
2068
2069    // User A does not hear users B or C.
2070    assert_eq!(
2071        participant_audio_state(&room_a, cx_a),
2072        &[
2073            ParticipantAudioState {
2074                user_id: client_b.user_id().unwrap(),
2075                is_muted: false,
2076                audio_tracks_playing: vec![false],
2077            },
2078            ParticipantAudioState {
2079                user_id: client_c.user_id().unwrap(),
2080                is_muted: false,
2081                audio_tracks_playing: vec![false],
2082            }
2083        ]
2084    );
2085    assert_eq!(
2086        participant_audio_state(&room_b, cx_b),
2087        &[
2088            ParticipantAudioState {
2089                user_id: client_a.user_id().unwrap(),
2090                is_muted: true,
2091                audio_tracks_playing: vec![true],
2092            },
2093            ParticipantAudioState {
2094                user_id: client_c.user_id().unwrap(),
2095                is_muted: false,
2096                audio_tracks_playing: vec![true],
2097            }
2098        ]
2099    );
2100
2101    #[derive(PartialEq, Eq, Debug)]
2102    struct ParticipantAudioState {
2103        user_id: u64,
2104        is_muted: bool,
2105        audio_tracks_playing: Vec<bool>,
2106    }
2107
2108    fn participant_audio_state(
2109        room: &Entity<Room>,
2110        cx: &TestAppContext,
2111    ) -> Vec<ParticipantAudioState> {
2112        room.read_with(cx, |room, _| {
2113            room.remote_participants()
2114                .iter()
2115                .map(|(user_id, participant)| ParticipantAudioState {
2116                    user_id: *user_id,
2117                    is_muted: participant.muted,
2118                    audio_tracks_playing: participant
2119                        .audio_tracks
2120                        .values()
2121                        .map(|(track, _)| track.enabled())
2122                        .collect(),
2123                })
2124                .collect::<Vec<_>>()
2125        })
2126    }
2127}
2128
2129#[gpui::test(iterations = 10)]
2130async fn test_room_location(
2131    executor: BackgroundExecutor,
2132    cx_a: &mut TestAppContext,
2133    cx_b: &mut TestAppContext,
2134) {
2135    let mut server = TestServer::start(executor.clone()).await;
2136    let client_a = server.create_client(cx_a, "user_a").await;
2137    let client_b = server.create_client(cx_b, "user_b").await;
2138    client_a.fs().insert_tree("/a", json!({})).await;
2139    client_b.fs().insert_tree("/b", json!({})).await;
2140
2141    let active_call_a = cx_a.read(ActiveCall::global);
2142    let active_call_b = cx_b.read(ActiveCall::global);
2143
2144    let a_notified = Rc::new(Cell::new(false));
2145    cx_a.update({
2146        let notified = a_notified.clone();
2147        |cx| {
2148            cx.observe(&active_call_a, move |_, _| notified.set(true))
2149                .detach()
2150        }
2151    });
2152
2153    let b_notified = Rc::new(Cell::new(false));
2154    cx_b.update({
2155        let b_notified = b_notified.clone();
2156        |cx| {
2157            cx.observe(&active_call_b, move |_, _| b_notified.set(true))
2158                .detach()
2159        }
2160    });
2161
2162    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
2163    active_call_a
2164        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
2165        .await
2166        .unwrap();
2167    let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
2168
2169    server
2170        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2171        .await;
2172
2173    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
2174
2175    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
2176    executor.run_until_parked();
2177    assert!(a_notified.take());
2178    assert_eq!(
2179        participant_locations(&room_a, cx_a),
2180        vec![("user_b".to_string(), ParticipantLocation::External)]
2181    );
2182    assert!(b_notified.take());
2183    assert_eq!(
2184        participant_locations(&room_b, cx_b),
2185        vec![("user_a".to_string(), ParticipantLocation::UnsharedProject)]
2186    );
2187
2188    let project_a_id = active_call_a
2189        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2190        .await
2191        .unwrap();
2192    executor.run_until_parked();
2193    assert!(a_notified.take());
2194    assert_eq!(
2195        participant_locations(&room_a, cx_a),
2196        vec![("user_b".to_string(), ParticipantLocation::External)]
2197    );
2198    assert!(b_notified.take());
2199    assert_eq!(
2200        participant_locations(&room_b, cx_b),
2201        vec![(
2202            "user_a".to_string(),
2203            ParticipantLocation::SharedProject {
2204                project_id: project_a_id
2205            }
2206        )]
2207    );
2208
2209    let project_b_id = active_call_b
2210        .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
2211        .await
2212        .unwrap();
2213    executor.run_until_parked();
2214    assert!(a_notified.take());
2215    assert_eq!(
2216        participant_locations(&room_a, cx_a),
2217        vec![("user_b".to_string(), ParticipantLocation::External)]
2218    );
2219    assert!(b_notified.take());
2220    assert_eq!(
2221        participant_locations(&room_b, cx_b),
2222        vec![(
2223            "user_a".to_string(),
2224            ParticipantLocation::SharedProject {
2225                project_id: project_a_id
2226            }
2227        )]
2228    );
2229
2230    active_call_b
2231        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
2232        .await
2233        .unwrap();
2234    executor.run_until_parked();
2235    assert!(a_notified.take());
2236    assert_eq!(
2237        participant_locations(&room_a, cx_a),
2238        vec![(
2239            "user_b".to_string(),
2240            ParticipantLocation::SharedProject {
2241                project_id: project_b_id
2242            }
2243        )]
2244    );
2245    assert!(b_notified.take());
2246    assert_eq!(
2247        participant_locations(&room_b, cx_b),
2248        vec![(
2249            "user_a".to_string(),
2250            ParticipantLocation::SharedProject {
2251                project_id: project_a_id
2252            }
2253        )]
2254    );
2255
2256    active_call_b
2257        .update(cx_b, |call, cx| call.set_location(None, cx))
2258        .await
2259        .unwrap();
2260    executor.run_until_parked();
2261    assert!(a_notified.take());
2262    assert_eq!(
2263        participant_locations(&room_a, cx_a),
2264        vec![("user_b".to_string(), ParticipantLocation::External)]
2265    );
2266    assert!(b_notified.take());
2267    assert_eq!(
2268        participant_locations(&room_b, cx_b),
2269        vec![(
2270            "user_a".to_string(),
2271            ParticipantLocation::SharedProject {
2272                project_id: project_a_id
2273            }
2274        )]
2275    );
2276
2277    fn participant_locations(
2278        room: &Entity<Room>,
2279        cx: &TestAppContext,
2280    ) -> Vec<(String, ParticipantLocation)> {
2281        room.read_with(cx, |room, _| {
2282            room.remote_participants()
2283                .values()
2284                .map(|participant| {
2285                    (
2286                        participant.user.github_login.to_string(),
2287                        participant.location,
2288                    )
2289                })
2290                .collect()
2291        })
2292    }
2293}
2294
2295#[gpui::test(iterations = 10)]
2296async fn test_propagate_saves_and_fs_changes(
2297    executor: BackgroundExecutor,
2298    cx_a: &mut TestAppContext,
2299    cx_b: &mut TestAppContext,
2300    cx_c: &mut TestAppContext,
2301) {
2302    let mut server = TestServer::start(executor.clone()).await;
2303    let client_a = server.create_client(cx_a, "user_a").await;
2304    let client_b = server.create_client(cx_b, "user_b").await;
2305    let client_c = server.create_client(cx_c, "user_c").await;
2306
2307    server
2308        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2309        .await;
2310    let active_call_a = cx_a.read(ActiveCall::global);
2311
2312    let rust = Arc::new(Language::new(
2313        LanguageConfig {
2314            name: "Rust".into(),
2315            matcher: LanguageMatcher {
2316                path_suffixes: vec!["rs".to_string()],
2317                ..Default::default()
2318            },
2319            ..Default::default()
2320        },
2321        Some(tree_sitter_rust::LANGUAGE.into()),
2322    ));
2323    let javascript = Arc::new(Language::new(
2324        LanguageConfig {
2325            name: "JavaScript".into(),
2326            matcher: LanguageMatcher {
2327                path_suffixes: vec!["js".to_string()],
2328                ..Default::default()
2329            },
2330            ..Default::default()
2331        },
2332        Some(tree_sitter_rust::LANGUAGE.into()),
2333    ));
2334    for client in [&client_a, &client_b, &client_c] {
2335        client.language_registry().add(rust.clone());
2336        client.language_registry().add(javascript.clone());
2337    }
2338
2339    client_a
2340        .fs()
2341        .insert_tree(
2342            path!("/a"),
2343            json!({
2344                "file1.rs": "",
2345                "file2": ""
2346            }),
2347        )
2348        .await;
2349    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
2350
2351    let worktree_a = project_a.read_with(cx_a, |p, cx| p.worktrees(cx).next().unwrap());
2352    let project_id = active_call_a
2353        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
2354        .await
2355        .unwrap();
2356
2357    // Join that worktree as clients B and C.
2358    let project_b = client_b.join_remote_project(project_id, cx_b).await;
2359    let project_c = client_c.join_remote_project(project_id, cx_c).await;
2360
2361    let worktree_b = project_b.read_with(cx_b, |p, cx| p.worktrees(cx).next().unwrap());
2362
2363    let worktree_c = project_c.read_with(cx_c, |p, cx| p.worktrees(cx).next().unwrap());
2364
2365    // Open and edit a buffer as both guests B and C.
2366    let buffer_b = project_b
2367        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2368        .await
2369        .unwrap();
2370    let buffer_c = project_c
2371        .update(cx_c, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2372        .await
2373        .unwrap();
2374
2375    buffer_b.read_with(cx_b, |buffer, _| {
2376        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2377    });
2378
2379    buffer_c.read_with(cx_c, |buffer, _| {
2380        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
2381    });
2382    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "i-am-b, ")], None, cx));
2383    buffer_c.update(cx_c, |buf, cx| buf.edit([(0..0, "i-am-c, ")], None, cx));
2384
2385    // Open and edit that buffer as the host.
2386    let buffer_a = project_a
2387        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "file1.rs"), cx))
2388        .await
2389        .unwrap();
2390
2391    executor.run_until_parked();
2392
2393    buffer_a.read_with(cx_a, |buf, _| assert_eq!(buf.text(), "i-am-c, i-am-b, "));
2394    buffer_a.update(cx_a, |buf, cx| {
2395        buf.edit([(buf.len()..buf.len(), "i-am-a")], None, cx)
2396    });
2397
2398    executor.run_until_parked();
2399
2400    buffer_a.read_with(cx_a, |buf, _| {
2401        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2402    });
2403
2404    buffer_b.read_with(cx_b, |buf, _| {
2405        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2406    });
2407
2408    buffer_c.read_with(cx_c, |buf, _| {
2409        assert_eq!(buf.text(), "i-am-c, i-am-b, i-am-a");
2410    });
2411
2412    // Edit the buffer as the host and concurrently save as guest B.
2413    let save_b = project_b.update(cx_b, |project, cx| {
2414        project.save_buffer(buffer_b.clone(), cx)
2415    });
2416    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "hi-a, ")], None, cx));
2417    save_b.await.unwrap();
2418    assert_eq!(
2419        client_a.fs().load("/a/file1.rs".as_ref()).await.unwrap(),
2420        "hi-a, i-am-c, i-am-b, i-am-a"
2421    );
2422
2423    executor.run_until_parked();
2424
2425    buffer_a.read_with(cx_a, |buf, _| assert!(!buf.is_dirty()));
2426
2427    buffer_b.read_with(cx_b, |buf, _| assert!(!buf.is_dirty()));
2428
2429    buffer_c.read_with(cx_c, |buf, _| assert!(!buf.is_dirty()));
2430
2431    // Make changes on host's file system, see those changes on guest worktrees.
2432    client_a
2433        .fs()
2434        .rename(
2435            path!("/a/file1.rs").as_ref(),
2436            path!("/a/file1.js").as_ref(),
2437            Default::default(),
2438        )
2439        .await
2440        .unwrap();
2441    client_a
2442        .fs()
2443        .rename(
2444            path!("/a/file2").as_ref(),
2445            path!("/a/file3").as_ref(),
2446            Default::default(),
2447        )
2448        .await
2449        .unwrap();
2450    client_a
2451        .fs()
2452        .insert_file(path!("/a/file4"), "4".into())
2453        .await;
2454    executor.run_until_parked();
2455
2456    worktree_a.read_with(cx_a, |tree, _| {
2457        assert_eq!(
2458            tree.paths()
2459                .map(|p| p.to_string_lossy())
2460                .collect::<Vec<_>>(),
2461            ["file1.js", "file3", "file4"]
2462        )
2463    });
2464
2465    worktree_b.read_with(cx_b, |tree, _| {
2466        assert_eq!(
2467            tree.paths()
2468                .map(|p| p.to_string_lossy())
2469                .collect::<Vec<_>>(),
2470            ["file1.js", "file3", "file4"]
2471        )
2472    });
2473
2474    worktree_c.read_with(cx_c, |tree, _| {
2475        assert_eq!(
2476            tree.paths()
2477                .map(|p| p.to_string_lossy())
2478                .collect::<Vec<_>>(),
2479            ["file1.js", "file3", "file4"]
2480        )
2481    });
2482
2483    // Ensure buffer files are updated as well.
2484
2485    buffer_a.read_with(cx_a, |buffer, _| {
2486        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2487        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2488    });
2489
2490    buffer_b.read_with(cx_b, |buffer, _| {
2491        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2492        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2493    });
2494
2495    buffer_c.read_with(cx_c, |buffer, _| {
2496        assert_eq!(buffer.file().unwrap().path().to_str(), Some("file1.js"));
2497        assert_eq!(buffer.language().unwrap().name(), "JavaScript".into());
2498    });
2499
2500    let new_buffer_a = project_a
2501        .update(cx_a, |p, cx| p.create_buffer(cx))
2502        .await
2503        .unwrap();
2504
2505    let new_buffer_id = new_buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id());
2506    let new_buffer_b = project_b
2507        .update(cx_b, |p, cx| p.open_buffer_by_id(new_buffer_id, cx))
2508        .await
2509        .unwrap();
2510
2511    new_buffer_b.read_with(cx_b, |buffer, _| {
2512        assert!(buffer.file().is_none());
2513    });
2514
2515    new_buffer_a.update(cx_a, |buffer, cx| {
2516        buffer.edit([(0..0, "ok")], None, cx);
2517    });
2518    project_a
2519        .update(cx_a, |project, cx| {
2520            let path = ProjectPath {
2521                path: Arc::from(Path::new("file3.rs")),
2522                worktree_id: worktree_a.read(cx).id(),
2523            };
2524
2525            project.save_buffer_as(new_buffer_a.clone(), path, cx)
2526        })
2527        .await
2528        .unwrap();
2529
2530    executor.run_until_parked();
2531
2532    new_buffer_b.read_with(cx_b, |buffer_b, _| {
2533        assert_eq!(
2534            buffer_b.file().unwrap().path().as_ref(),
2535            Path::new("file3.rs")
2536        );
2537
2538        new_buffer_a.read_with(cx_a, |buffer_a, _| {
2539            assert_eq!(buffer_b.saved_mtime(), buffer_a.saved_mtime());
2540            assert_eq!(buffer_b.saved_version(), buffer_a.saved_version());
2541        });
2542    });
2543}
2544
2545#[gpui::test(iterations = 10)]
2546async fn test_git_diff_base_change(
2547    executor: BackgroundExecutor,
2548    cx_a: &mut TestAppContext,
2549    cx_b: &mut TestAppContext,
2550) {
2551    let mut server = TestServer::start(executor.clone()).await;
2552    let client_a = server.create_client(cx_a, "user_a").await;
2553    let client_b = server.create_client(cx_b, "user_b").await;
2554    server
2555        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
2556        .await;
2557    let active_call_a = cx_a.read(ActiveCall::global);
2558
2559    client_a
2560        .fs()
2561        .insert_tree(
2562            "/dir",
2563            json!({
2564            ".git": {},
2565            "sub": {
2566                ".git": {},
2567                "b.txt": "
2568                    one
2569                    two
2570                    three
2571                ".unindent(),
2572            },
2573            "a.txt": "
2574                    one
2575                    two
2576                    three
2577                ".unindent(),
2578            }),
2579        )
2580        .await;
2581
2582    let (project_local, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2583    let project_id = active_call_a
2584        .update(cx_a, |call, cx| {
2585            call.share_project(project_local.clone(), cx)
2586        })
2587        .await
2588        .unwrap();
2589
2590    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2591
2592    let staged_text = "
2593        one
2594        three
2595    "
2596    .unindent();
2597
2598    let committed_text = "
2599        one
2600        TWO
2601        three
2602    "
2603    .unindent();
2604
2605    let new_committed_text = "
2606        one
2607        TWO_HUNDRED
2608        three
2609    "
2610    .unindent();
2611
2612    let new_staged_text = "
2613        one
2614        two
2615    "
2616    .unindent();
2617
2618    client_a.fs().set_index_for_repo(
2619        Path::new("/dir/.git"),
2620        &[("a.txt".into(), staged_text.clone())],
2621    );
2622    client_a.fs().set_head_for_repo(
2623        Path::new("/dir/.git"),
2624        &[("a.txt".into(), committed_text.clone())],
2625        "deadbeef",
2626    );
2627
2628    // Create the buffer
2629    let buffer_local_a = project_local
2630        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2631        .await
2632        .unwrap();
2633    let local_unstaged_diff_a = project_local
2634        .update(cx_a, |p, cx| {
2635            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2636        })
2637        .await
2638        .unwrap();
2639
2640    // Wait for it to catch up to the new diff
2641    executor.run_until_parked();
2642    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2643        let buffer = buffer_local_a.read(cx);
2644        assert_eq!(
2645            diff.base_text_string().as_deref(),
2646            Some(staged_text.as_str())
2647        );
2648        assert_hunks(
2649            diff.hunks_in_row_range(0..4, buffer, cx),
2650            buffer,
2651            &diff.base_text_string().unwrap(),
2652            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2653        );
2654    });
2655
2656    // Create remote buffer
2657    let remote_buffer_a = project_remote
2658        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2659        .await
2660        .unwrap();
2661    let remote_unstaged_diff_a = project_remote
2662        .update(cx_b, |p, cx| {
2663            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2664        })
2665        .await
2666        .unwrap();
2667
2668    // Wait remote buffer to catch up to the new diff
2669    executor.run_until_parked();
2670    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2671        let buffer = remote_buffer_a.read(cx);
2672        assert_eq!(
2673            diff.base_text_string().as_deref(),
2674            Some(staged_text.as_str())
2675        );
2676        assert_hunks(
2677            diff.hunks_in_row_range(0..4, buffer, cx),
2678            buffer,
2679            &diff.base_text_string().unwrap(),
2680            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2681        );
2682    });
2683
2684    // Open uncommitted changes on the guest, without opening them on the host first
2685    let remote_uncommitted_diff_a = project_remote
2686        .update(cx_b, |p, cx| {
2687            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2688        })
2689        .await
2690        .unwrap();
2691    executor.run_until_parked();
2692    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2693        let buffer = remote_buffer_a.read(cx);
2694        assert_eq!(
2695            diff.base_text_string().as_deref(),
2696            Some(committed_text.as_str())
2697        );
2698        assert_hunks(
2699            diff.hunks_in_row_range(0..4, buffer, cx),
2700            buffer,
2701            &diff.base_text_string().unwrap(),
2702            &[(
2703                1..2,
2704                "TWO\n",
2705                "two\n",
2706                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2707            )],
2708        );
2709    });
2710
2711    // Update the index text of the open buffer
2712    client_a.fs().set_index_for_repo(
2713        Path::new("/dir/.git"),
2714        &[("a.txt".into(), new_staged_text.clone())],
2715    );
2716    client_a.fs().set_head_for_repo(
2717        Path::new("/dir/.git"),
2718        &[("a.txt".into(), new_committed_text.clone())],
2719        "deadbeef",
2720    );
2721
2722    // Wait for buffer_local_a to receive it
2723    executor.run_until_parked();
2724    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2725        let buffer = buffer_local_a.read(cx);
2726        assert_eq!(
2727            diff.base_text_string().as_deref(),
2728            Some(new_staged_text.as_str())
2729        );
2730        assert_hunks(
2731            diff.hunks_in_row_range(0..4, buffer, cx),
2732            buffer,
2733            &diff.base_text_string().unwrap(),
2734            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2735        );
2736    });
2737
2738    // Guest receives index text update
2739    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2740        let buffer = remote_buffer_a.read(cx);
2741        assert_eq!(
2742            diff.base_text_string().as_deref(),
2743            Some(new_staged_text.as_str())
2744        );
2745        assert_hunks(
2746            diff.hunks_in_row_range(0..4, buffer, cx),
2747            buffer,
2748            &diff.base_text_string().unwrap(),
2749            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2750        );
2751    });
2752
2753    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2754        let buffer = remote_buffer_a.read(cx);
2755        assert_eq!(
2756            diff.base_text_string().as_deref(),
2757            Some(new_committed_text.as_str())
2758        );
2759        assert_hunks(
2760            diff.hunks_in_row_range(0..4, buffer, cx),
2761            buffer,
2762            &diff.base_text_string().unwrap(),
2763            &[(
2764                1..2,
2765                "TWO_HUNDRED\n",
2766                "two\n",
2767                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2768            )],
2769        );
2770    });
2771
2772    // Nested git dir
2773    let staged_text = "
2774        one
2775        three
2776    "
2777    .unindent();
2778
2779    let new_staged_text = "
2780        one
2781        two
2782    "
2783    .unindent();
2784
2785    client_a.fs().set_index_for_repo(
2786        Path::new("/dir/sub/.git"),
2787        &[("b.txt".into(), staged_text.clone())],
2788    );
2789
2790    // Create the buffer
2791    let buffer_local_b = project_local
2792        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2793        .await
2794        .unwrap();
2795    let local_unstaged_diff_b = project_local
2796        .update(cx_a, |p, cx| {
2797            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2798        })
2799        .await
2800        .unwrap();
2801
2802    // Wait for it to catch up to the new diff
2803    executor.run_until_parked();
2804    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2805        let buffer = buffer_local_b.read(cx);
2806        assert_eq!(
2807            diff.base_text_string().as_deref(),
2808            Some(staged_text.as_str())
2809        );
2810        assert_hunks(
2811            diff.hunks_in_row_range(0..4, buffer, cx),
2812            buffer,
2813            &diff.base_text_string().unwrap(),
2814            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2815        );
2816    });
2817
2818    // Create remote buffer
2819    let remote_buffer_b = project_remote
2820        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2821        .await
2822        .unwrap();
2823    let remote_unstaged_diff_b = project_remote
2824        .update(cx_b, |p, cx| {
2825            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2826        })
2827        .await
2828        .unwrap();
2829
2830    executor.run_until_parked();
2831    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2832        let buffer = remote_buffer_b.read(cx);
2833        assert_eq!(
2834            diff.base_text_string().as_deref(),
2835            Some(staged_text.as_str())
2836        );
2837        assert_hunks(
2838            diff.hunks_in_row_range(0..4, buffer, cx),
2839            buffer,
2840            &staged_text,
2841            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2842        );
2843    });
2844
2845    // Updatet the staged text
2846    client_a.fs().set_index_for_repo(
2847        Path::new("/dir/sub/.git"),
2848        &[("b.txt".into(), new_staged_text.clone())],
2849    );
2850
2851    // Wait for buffer_local_b to receive it
2852    executor.run_until_parked();
2853    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2854        let buffer = buffer_local_b.read(cx);
2855        assert_eq!(
2856            diff.base_text_string().as_deref(),
2857            Some(new_staged_text.as_str())
2858        );
2859        assert_hunks(
2860            diff.hunks_in_row_range(0..4, buffer, cx),
2861            buffer,
2862            &new_staged_text,
2863            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2864        );
2865    });
2866
2867    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2868        let buffer = remote_buffer_b.read(cx);
2869        assert_eq!(
2870            diff.base_text_string().as_deref(),
2871            Some(new_staged_text.as_str())
2872        );
2873        assert_hunks(
2874            diff.hunks_in_row_range(0..4, buffer, cx),
2875            buffer,
2876            &new_staged_text,
2877            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2878        );
2879    });
2880}
2881
2882#[gpui::test(iterations = 10)]
2883async fn test_git_branch_name(
2884    executor: BackgroundExecutor,
2885    cx_a: &mut TestAppContext,
2886    cx_b: &mut TestAppContext,
2887    cx_c: &mut TestAppContext,
2888) {
2889    let mut server = TestServer::start(executor.clone()).await;
2890    let client_a = server.create_client(cx_a, "user_a").await;
2891    let client_b = server.create_client(cx_b, "user_b").await;
2892    let client_c = server.create_client(cx_c, "user_c").await;
2893    server
2894        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2895        .await;
2896    let active_call_a = cx_a.read(ActiveCall::global);
2897
2898    client_a
2899        .fs()
2900        .insert_tree(
2901            "/dir",
2902            json!({
2903            ".git": {},
2904            }),
2905        )
2906        .await;
2907
2908    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2909    let project_id = active_call_a
2910        .update(cx_a, |call, cx| {
2911            call.share_project(project_local.clone(), cx)
2912        })
2913        .await
2914        .unwrap();
2915
2916    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2917    client_a
2918        .fs()
2919        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2920
2921    // Wait for it to catch up to the new branch
2922    executor.run_until_parked();
2923
2924    #[track_caller]
2925    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2926        let branch_name = branch_name.map(Into::into);
2927        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2928        assert_eq!(repositories.len(), 1);
2929        let repository = repositories[0].clone();
2930        assert_eq!(
2931            repository
2932                .read(cx)
2933                .branch
2934                .as_ref()
2935                .map(|branch| branch.name().to_owned()),
2936            branch_name
2937        )
2938    }
2939
2940    // Smoke test branch reading
2941
2942    project_local.read_with(cx_a, |project, cx| {
2943        assert_branch(Some("branch-1"), project, cx)
2944    });
2945
2946    project_remote.read_with(cx_b, |project, cx| {
2947        assert_branch(Some("branch-1"), project, cx)
2948    });
2949
2950    client_a
2951        .fs()
2952        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2953
2954    // Wait for buffer_local_a to receive it
2955    executor.run_until_parked();
2956
2957    // Smoke test branch reading
2958
2959    project_local.read_with(cx_a, |project, cx| {
2960        assert_branch(Some("branch-2"), project, cx)
2961    });
2962
2963    project_remote.read_with(cx_b, |project, cx| {
2964        assert_branch(Some("branch-2"), project, cx)
2965    });
2966
2967    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2968    executor.run_until_parked();
2969
2970    project_remote_c.read_with(cx_c, |project, cx| {
2971        assert_branch(Some("branch-2"), project, cx)
2972    });
2973}
2974
2975#[gpui::test]
2976async fn test_git_status_sync(
2977    executor: BackgroundExecutor,
2978    cx_a: &mut TestAppContext,
2979    cx_b: &mut TestAppContext,
2980    cx_c: &mut TestAppContext,
2981) {
2982    let mut server = TestServer::start(executor.clone()).await;
2983    let client_a = server.create_client(cx_a, "user_a").await;
2984    let client_b = server.create_client(cx_b, "user_b").await;
2985    let client_c = server.create_client(cx_c, "user_c").await;
2986    server
2987        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2988        .await;
2989    let active_call_a = cx_a.read(ActiveCall::global);
2990
2991    client_a
2992        .fs()
2993        .insert_tree(
2994            path!("/dir"),
2995            json!({
2996                ".git": {},
2997                "a.txt": "a",
2998                "b.txt": "b",
2999                "c.txt": "c",
3000            }),
3001        )
3002        .await;
3003
3004    // Initially, a.txt is uncommitted, but present in the index,
3005    // and b.txt is unmerged.
3006    client_a.fs().set_head_for_repo(
3007        path!("/dir/.git").as_ref(),
3008        &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
3009        "deadbeef",
3010    );
3011    client_a.fs().set_index_for_repo(
3012        path!("/dir/.git").as_ref(),
3013        &[
3014            ("a.txt".into(), "".into()),
3015            ("b.txt".into(), "B".into()),
3016            ("c.txt".into(), "c".into()),
3017        ],
3018    );
3019    client_a.fs().set_unmerged_paths_for_repo(
3020        path!("/dir/.git").as_ref(),
3021        &[(
3022            "b.txt".into(),
3023            UnmergedStatus {
3024                first_head: UnmergedStatusCode::Updated,
3025                second_head: UnmergedStatusCode::Deleted,
3026            },
3027        )],
3028    );
3029
3030    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3031        index_status: StatusCode::Added,
3032        worktree_status: StatusCode::Modified,
3033    });
3034    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3035        first_head: UnmergedStatusCode::Updated,
3036        second_head: UnmergedStatusCode::Deleted,
3037    });
3038
3039    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3040    let project_id = active_call_a
3041        .update(cx_a, |call, cx| {
3042            call.share_project(project_local.clone(), cx)
3043        })
3044        .await
3045        .unwrap();
3046
3047    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3048
3049    // Wait for it to catch up to the new status
3050    executor.run_until_parked();
3051
3052    #[track_caller]
3053    fn assert_status(
3054        file: impl AsRef<Path>,
3055        status: Option<FileStatus>,
3056        project: &Project,
3057        cx: &App,
3058    ) {
3059        let file = file.as_ref();
3060        let repos = project
3061            .repositories(cx)
3062            .values()
3063            .cloned()
3064            .collect::<Vec<_>>();
3065        assert_eq!(repos.len(), 1);
3066        let repo = repos.into_iter().next().unwrap();
3067        assert_eq!(
3068            repo.read(cx)
3069                .status_for_path(&file.into())
3070                .map(|entry| entry.status),
3071            status
3072        );
3073    }
3074
3075    project_local.read_with(cx_a, |project, cx| {
3076        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3077        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3078        assert_status("c.txt", None, project, cx);
3079    });
3080
3081    project_remote.read_with(cx_b, |project, cx| {
3082        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3083        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3084        assert_status("c.txt", None, project, cx);
3085    });
3086
3087    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3088        index_status: StatusCode::Added,
3089        worktree_status: StatusCode::Unmodified,
3090    });
3091    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3092        index_status: StatusCode::Deleted,
3093        worktree_status: StatusCode::Added,
3094    });
3095    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3096        index_status: StatusCode::Unmodified,
3097        worktree_status: StatusCode::Modified,
3098    });
3099
3100    // Delete b.txt from the index, mark conflict as resolved,
3101    // and modify c.txt in the working copy.
3102    client_a.fs().set_index_for_repo(
3103        path!("/dir/.git").as_ref(),
3104        &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
3105    );
3106    client_a
3107        .fs()
3108        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3109    client_a
3110        .fs()
3111        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3112        .await
3113        .unwrap();
3114
3115    // Wait for buffer_local_a to receive it
3116    executor.run_until_parked();
3117
3118    // Smoke test status reading
3119    project_local.read_with(cx_a, |project, cx| {
3120        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3121        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3122        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3123    });
3124
3125    project_remote.read_with(cx_b, |project, cx| {
3126        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3127        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3128        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3129    });
3130
3131    // And synchronization while joining
3132    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3133    executor.run_until_parked();
3134
3135    project_remote_c.read_with(cx_c, |project, cx| {
3136        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3137        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3138        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3139    });
3140
3141    // Now remove the original git repository and check that collaborators are notified.
3142    client_a
3143        .fs()
3144        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3145        .await
3146        .unwrap();
3147
3148    executor.run_until_parked();
3149    project_remote.update(cx_b, |project, cx| {
3150        pretty_assertions::assert_eq!(
3151            project.git_store().read(cx).repo_snapshots(cx),
3152            HashMap::default()
3153        );
3154    });
3155    project_remote_c.update(cx_c, |project, cx| {
3156        pretty_assertions::assert_eq!(
3157            project.git_store().read(cx).repo_snapshots(cx),
3158            HashMap::default()
3159        );
3160    });
3161}
3162
3163#[gpui::test(iterations = 10)]
3164async fn test_fs_operations(
3165    executor: BackgroundExecutor,
3166    cx_a: &mut TestAppContext,
3167    cx_b: &mut TestAppContext,
3168) {
3169    let mut server = TestServer::start(executor.clone()).await;
3170    let client_a = server.create_client(cx_a, "user_a").await;
3171    let client_b = server.create_client(cx_b, "user_b").await;
3172    server
3173        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3174        .await;
3175    let active_call_a = cx_a.read(ActiveCall::global);
3176
3177    client_a
3178        .fs()
3179        .insert_tree(
3180            path!("/dir"),
3181            json!({
3182                "a.txt": "a-contents",
3183                "b.txt": "b-contents",
3184            }),
3185        )
3186        .await;
3187    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3188    let project_id = active_call_a
3189        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3190        .await
3191        .unwrap();
3192    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3193
3194    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3195    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3196
3197    let entry = project_b
3198        .update(cx_b, |project, cx| {
3199            project.create_entry((worktree_id, "c.txt"), false, cx)
3200        })
3201        .await
3202        .unwrap()
3203        .to_included()
3204        .unwrap();
3205
3206    worktree_a.read_with(cx_a, |worktree, _| {
3207        assert_eq!(
3208            worktree
3209                .paths()
3210                .map(|p| p.to_string_lossy())
3211                .collect::<Vec<_>>(),
3212            ["a.txt", "b.txt", "c.txt"]
3213        );
3214    });
3215
3216    worktree_b.read_with(cx_b, |worktree, _| {
3217        assert_eq!(
3218            worktree
3219                .paths()
3220                .map(|p| p.to_string_lossy())
3221                .collect::<Vec<_>>(),
3222            ["a.txt", "b.txt", "c.txt"]
3223        );
3224    });
3225
3226    project_b
3227        .update(cx_b, |project, cx| {
3228            project.rename_entry(entry.id, Path::new("d.txt"), cx)
3229        })
3230        .await
3231        .unwrap()
3232        .to_included()
3233        .unwrap();
3234
3235    worktree_a.read_with(cx_a, |worktree, _| {
3236        assert_eq!(
3237            worktree
3238                .paths()
3239                .map(|p| p.to_string_lossy())
3240                .collect::<Vec<_>>(),
3241            ["a.txt", "b.txt", "d.txt"]
3242        );
3243    });
3244
3245    worktree_b.read_with(cx_b, |worktree, _| {
3246        assert_eq!(
3247            worktree
3248                .paths()
3249                .map(|p| p.to_string_lossy())
3250                .collect::<Vec<_>>(),
3251            ["a.txt", "b.txt", "d.txt"]
3252        );
3253    });
3254
3255    let dir_entry = project_b
3256        .update(cx_b, |project, cx| {
3257            project.create_entry((worktree_id, "DIR"), true, cx)
3258        })
3259        .await
3260        .unwrap()
3261        .to_included()
3262        .unwrap();
3263
3264    worktree_a.read_with(cx_a, |worktree, _| {
3265        assert_eq!(
3266            worktree
3267                .paths()
3268                .map(|p| p.to_string_lossy())
3269                .collect::<Vec<_>>(),
3270            ["DIR", "a.txt", "b.txt", "d.txt"]
3271        );
3272    });
3273
3274    worktree_b.read_with(cx_b, |worktree, _| {
3275        assert_eq!(
3276            worktree
3277                .paths()
3278                .map(|p| p.to_string_lossy())
3279                .collect::<Vec<_>>(),
3280            ["DIR", "a.txt", "b.txt", "d.txt"]
3281        );
3282    });
3283
3284    project_b
3285        .update(cx_b, |project, cx| {
3286            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
3287        })
3288        .await
3289        .unwrap()
3290        .to_included()
3291        .unwrap();
3292
3293    project_b
3294        .update(cx_b, |project, cx| {
3295            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
3296        })
3297        .await
3298        .unwrap()
3299        .to_included()
3300        .unwrap();
3301
3302    project_b
3303        .update(cx_b, |project, cx| {
3304            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
3305        })
3306        .await
3307        .unwrap()
3308        .to_included()
3309        .unwrap();
3310
3311    worktree_a.read_with(cx_a, |worktree, _| {
3312        assert_eq!(
3313            worktree
3314                .paths()
3315                .map(|p| p.to_string_lossy())
3316                .collect::<Vec<_>>(),
3317            [
3318                path!("DIR"),
3319                path!("DIR/SUBDIR"),
3320                path!("DIR/SUBDIR/f.txt"),
3321                path!("DIR/e.txt"),
3322                path!("a.txt"),
3323                path!("b.txt"),
3324                path!("d.txt")
3325            ]
3326        );
3327    });
3328
3329    worktree_b.read_with(cx_b, |worktree, _| {
3330        assert_eq!(
3331            worktree
3332                .paths()
3333                .map(|p| p.to_string_lossy())
3334                .collect::<Vec<_>>(),
3335            [
3336                path!("DIR"),
3337                path!("DIR/SUBDIR"),
3338                path!("DIR/SUBDIR/f.txt"),
3339                path!("DIR/e.txt"),
3340                path!("a.txt"),
3341                path!("b.txt"),
3342                path!("d.txt")
3343            ]
3344        );
3345    });
3346
3347    project_b
3348        .update(cx_b, |project, cx| {
3349            project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
3350        })
3351        .await
3352        .unwrap()
3353        .unwrap();
3354
3355    worktree_a.read_with(cx_a, |worktree, _| {
3356        assert_eq!(
3357            worktree
3358                .paths()
3359                .map(|p| p.to_string_lossy())
3360                .collect::<Vec<_>>(),
3361            [
3362                path!("DIR"),
3363                path!("DIR/SUBDIR"),
3364                path!("DIR/SUBDIR/f.txt"),
3365                path!("DIR/e.txt"),
3366                path!("a.txt"),
3367                path!("b.txt"),
3368                path!("d.txt"),
3369                path!("f.txt")
3370            ]
3371        );
3372    });
3373
3374    worktree_b.read_with(cx_b, |worktree, _| {
3375        assert_eq!(
3376            worktree
3377                .paths()
3378                .map(|p| p.to_string_lossy())
3379                .collect::<Vec<_>>(),
3380            [
3381                path!("DIR"),
3382                path!("DIR/SUBDIR"),
3383                path!("DIR/SUBDIR/f.txt"),
3384                path!("DIR/e.txt"),
3385                path!("a.txt"),
3386                path!("b.txt"),
3387                path!("d.txt"),
3388                path!("f.txt")
3389            ]
3390        );
3391    });
3392
3393    project_b
3394        .update(cx_b, |project, cx| {
3395            project.delete_entry(dir_entry.id, false, cx).unwrap()
3396        })
3397        .await
3398        .unwrap();
3399    executor.run_until_parked();
3400
3401    worktree_a.read_with(cx_a, |worktree, _| {
3402        assert_eq!(
3403            worktree
3404                .paths()
3405                .map(|p| p.to_string_lossy())
3406                .collect::<Vec<_>>(),
3407            ["a.txt", "b.txt", "d.txt", "f.txt"]
3408        );
3409    });
3410
3411    worktree_b.read_with(cx_b, |worktree, _| {
3412        assert_eq!(
3413            worktree
3414                .paths()
3415                .map(|p| p.to_string_lossy())
3416                .collect::<Vec<_>>(),
3417            ["a.txt", "b.txt", "d.txt", "f.txt"]
3418        );
3419    });
3420
3421    project_b
3422        .update(cx_b, |project, cx| {
3423            project.delete_entry(entry.id, false, cx).unwrap()
3424        })
3425        .await
3426        .unwrap();
3427
3428    worktree_a.read_with(cx_a, |worktree, _| {
3429        assert_eq!(
3430            worktree
3431                .paths()
3432                .map(|p| p.to_string_lossy())
3433                .collect::<Vec<_>>(),
3434            ["a.txt", "b.txt", "f.txt"]
3435        );
3436    });
3437
3438    worktree_b.read_with(cx_b, |worktree, _| {
3439        assert_eq!(
3440            worktree
3441                .paths()
3442                .map(|p| p.to_string_lossy())
3443                .collect::<Vec<_>>(),
3444            ["a.txt", "b.txt", "f.txt"]
3445        );
3446    });
3447}
3448
3449#[gpui::test(iterations = 10)]
3450async fn test_local_settings(
3451    executor: BackgroundExecutor,
3452    cx_a: &mut TestAppContext,
3453    cx_b: &mut TestAppContext,
3454) {
3455    let mut server = TestServer::start(executor.clone()).await;
3456    let client_a = server.create_client(cx_a, "user_a").await;
3457    let client_b = server.create_client(cx_b, "user_b").await;
3458    server
3459        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3460        .await;
3461    let active_call_a = cx_a.read(ActiveCall::global);
3462
3463    // As client A, open a project that contains some local settings files
3464    client_a
3465        .fs()
3466        .insert_tree(
3467            "/dir",
3468            json!({
3469                ".zed": {
3470                    "settings.json": r#"{ "tab_size": 2 }"#
3471                },
3472                "a": {
3473                    ".zed": {
3474                        "settings.json": r#"{ "tab_size": 8 }"#
3475                    },
3476                    "a.txt": "a-contents",
3477                },
3478                "b": {
3479                    "b.txt": "b-contents",
3480                }
3481            }),
3482        )
3483        .await;
3484    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3485    executor.run_until_parked();
3486    let project_id = active_call_a
3487        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3488        .await
3489        .unwrap();
3490    executor.run_until_parked();
3491
3492    // As client B, join that project and observe the local settings.
3493    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3494
3495    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3496    executor.run_until_parked();
3497    cx_b.read(|cx| {
3498        let store = cx.global::<SettingsStore>();
3499        assert_eq!(
3500            store
3501                .local_settings(worktree_b.read(cx).id())
3502                .collect::<Vec<_>>(),
3503            &[
3504                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
3505                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3506            ]
3507        )
3508    });
3509
3510    // As client A, update a settings file. As Client B, see the changed settings.
3511    client_a
3512        .fs()
3513        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3514        .await;
3515    executor.run_until_parked();
3516    cx_b.read(|cx| {
3517        let store = cx.global::<SettingsStore>();
3518        assert_eq!(
3519            store
3520                .local_settings(worktree_b.read(cx).id())
3521                .collect::<Vec<_>>(),
3522            &[
3523                (Path::new("").into(), r#"{}"#.to_string()),
3524                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3525            ]
3526        )
3527    });
3528
3529    // As client A, create and remove some settings files. As client B, see the changed settings.
3530    client_a
3531        .fs()
3532        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3533        .await
3534        .unwrap();
3535    client_a
3536        .fs()
3537        .create_dir("/dir/b/.zed".as_ref())
3538        .await
3539        .unwrap();
3540    client_a
3541        .fs()
3542        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3543        .await;
3544    executor.run_until_parked();
3545    cx_b.read(|cx| {
3546        let store = cx.global::<SettingsStore>();
3547        assert_eq!(
3548            store
3549                .local_settings(worktree_b.read(cx).id())
3550                .collect::<Vec<_>>(),
3551            &[
3552                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3553                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
3554            ]
3555        )
3556    });
3557
3558    // As client B, disconnect.
3559    server.forbid_connections();
3560    server.disconnect_client(client_b.peer_id().unwrap());
3561
3562    // As client A, change and remove settings files while client B is disconnected.
3563    client_a
3564        .fs()
3565        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3566        .await;
3567    client_a
3568        .fs()
3569        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3570        .await
3571        .unwrap();
3572    executor.run_until_parked();
3573
3574    // As client B, reconnect and see the changed settings.
3575    server.allow_connections();
3576    executor.advance_clock(RECEIVE_TIMEOUT);
3577    cx_b.read(|cx| {
3578        let store = cx.global::<SettingsStore>();
3579        assert_eq!(
3580            store
3581                .local_settings(worktree_b.read(cx).id())
3582                .collect::<Vec<_>>(),
3583            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
3584        )
3585    });
3586}
3587
3588#[gpui::test(iterations = 10)]
3589async fn test_buffer_conflict_after_save(
3590    executor: BackgroundExecutor,
3591    cx_a: &mut TestAppContext,
3592    cx_b: &mut TestAppContext,
3593) {
3594    let mut server = TestServer::start(executor.clone()).await;
3595    let client_a = server.create_client(cx_a, "user_a").await;
3596    let client_b = server.create_client(cx_b, "user_b").await;
3597    server
3598        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3599        .await;
3600    let active_call_a = cx_a.read(ActiveCall::global);
3601
3602    client_a
3603        .fs()
3604        .insert_tree(
3605            path!("/dir"),
3606            json!({
3607                "a.txt": "a-contents",
3608            }),
3609        )
3610        .await;
3611    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3612    let project_id = active_call_a
3613        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3614        .await
3615        .unwrap();
3616    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3617
3618    // Open a buffer as client B
3619    let buffer_b = project_b
3620        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3621        .await
3622        .unwrap();
3623
3624    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3625
3626    buffer_b.read_with(cx_b, |buf, _| {
3627        assert!(buf.is_dirty());
3628        assert!(!buf.has_conflict());
3629    });
3630
3631    project_b
3632        .update(cx_b, |project, cx| {
3633            project.save_buffer(buffer_b.clone(), cx)
3634        })
3635        .await
3636        .unwrap();
3637
3638    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3639
3640    buffer_b.read_with(cx_b, |buf, _| {
3641        assert!(!buf.has_conflict());
3642    });
3643
3644    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3645
3646    buffer_b.read_with(cx_b, |buf, _| {
3647        assert!(buf.is_dirty());
3648        assert!(!buf.has_conflict());
3649    });
3650}
3651
3652#[gpui::test(iterations = 10)]
3653async fn test_buffer_reloading(
3654    executor: BackgroundExecutor,
3655    cx_a: &mut TestAppContext,
3656    cx_b: &mut TestAppContext,
3657) {
3658    let mut server = TestServer::start(executor.clone()).await;
3659    let client_a = server.create_client(cx_a, "user_a").await;
3660    let client_b = server.create_client(cx_b, "user_b").await;
3661    server
3662        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3663        .await;
3664    let active_call_a = cx_a.read(ActiveCall::global);
3665
3666    client_a
3667        .fs()
3668        .insert_tree(
3669            path!("/dir"),
3670            json!({
3671                "a.txt": "a\nb\nc",
3672            }),
3673        )
3674        .await;
3675    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3676    let project_id = active_call_a
3677        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3678        .await
3679        .unwrap();
3680    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3681
3682    // Open a buffer as client B
3683    let buffer_b = project_b
3684        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3685        .await
3686        .unwrap();
3687
3688    buffer_b.read_with(cx_b, |buf, _| {
3689        assert!(!buf.is_dirty());
3690        assert!(!buf.has_conflict());
3691        assert_eq!(buf.line_ending(), LineEnding::Unix);
3692    });
3693
3694    let new_contents = Rope::from("d\ne\nf");
3695    client_a
3696        .fs()
3697        .save(
3698            path!("/dir/a.txt").as_ref(),
3699            &new_contents,
3700            LineEnding::Windows,
3701        )
3702        .await
3703        .unwrap();
3704
3705    executor.run_until_parked();
3706
3707    buffer_b.read_with(cx_b, |buf, _| {
3708        assert_eq!(buf.text(), new_contents.to_string());
3709        assert!(!buf.is_dirty());
3710        assert!(!buf.has_conflict());
3711        assert_eq!(buf.line_ending(), LineEnding::Windows);
3712    });
3713}
3714
3715#[gpui::test(iterations = 10)]
3716async fn test_editing_while_guest_opens_buffer(
3717    executor: BackgroundExecutor,
3718    cx_a: &mut TestAppContext,
3719    cx_b: &mut TestAppContext,
3720) {
3721    let mut server = TestServer::start(executor.clone()).await;
3722    let client_a = server.create_client(cx_a, "user_a").await;
3723    let client_b = server.create_client(cx_b, "user_b").await;
3724    server
3725        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3726        .await;
3727    let active_call_a = cx_a.read(ActiveCall::global);
3728
3729    client_a
3730        .fs()
3731        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3732        .await;
3733    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3734    let project_id = active_call_a
3735        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3736        .await
3737        .unwrap();
3738    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3739
3740    // Open a buffer as client A
3741    let buffer_a = project_a
3742        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3743        .await
3744        .unwrap();
3745
3746    // Start opening the same buffer as client B
3747    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3748    let buffer_b = cx_b.executor().spawn(open_buffer);
3749
3750    // Edit the buffer as client A while client B is still opening it.
3751    cx_b.executor().simulate_random_delay().await;
3752    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3753    cx_b.executor().simulate_random_delay().await;
3754    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3755
3756    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3757    let buffer_b = buffer_b.await.unwrap();
3758    executor.run_until_parked();
3759
3760    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3761}
3762
3763#[gpui::test(iterations = 10)]
3764async fn test_leaving_worktree_while_opening_buffer(
3765    executor: BackgroundExecutor,
3766    cx_a: &mut TestAppContext,
3767    cx_b: &mut TestAppContext,
3768) {
3769    let mut server = TestServer::start(executor.clone()).await;
3770    let client_a = server.create_client(cx_a, "user_a").await;
3771    let client_b = server.create_client(cx_b, "user_b").await;
3772    server
3773        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3774        .await;
3775    let active_call_a = cx_a.read(ActiveCall::global);
3776
3777    client_a
3778        .fs()
3779        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3780        .await;
3781    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3782    let project_id = active_call_a
3783        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3784        .await
3785        .unwrap();
3786    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3787
3788    // See that a guest has joined as client A.
3789    executor.run_until_parked();
3790
3791    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3792
3793    // Begin opening a buffer as client B, but leave the project before the open completes.
3794    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3795    let buffer_b = cx_b.executor().spawn(open_buffer);
3796    cx_b.update(|_| drop(project_b));
3797    drop(buffer_b);
3798
3799    // See that the guest has left.
3800    executor.run_until_parked();
3801
3802    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3803}
3804
3805#[gpui::test(iterations = 10)]
3806async fn test_canceling_buffer_opening(
3807    executor: BackgroundExecutor,
3808    cx_a: &mut TestAppContext,
3809    cx_b: &mut TestAppContext,
3810) {
3811    let mut server = TestServer::start(executor.clone()).await;
3812    let client_a = server.create_client(cx_a, "user_a").await;
3813    let client_b = server.create_client(cx_b, "user_b").await;
3814    server
3815        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3816        .await;
3817    let active_call_a = cx_a.read(ActiveCall::global);
3818
3819    client_a
3820        .fs()
3821        .insert_tree(
3822            "/dir",
3823            json!({
3824                "a.txt": "abc",
3825            }),
3826        )
3827        .await;
3828    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3829    let project_id = active_call_a
3830        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3831        .await
3832        .unwrap();
3833    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3834
3835    let buffer_a = project_a
3836        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3837        .await
3838        .unwrap();
3839
3840    // Open a buffer as client B but cancel after a random amount of time.
3841    let buffer_b = project_b.update(cx_b, |p, cx| {
3842        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3843    });
3844    executor.simulate_random_delay().await;
3845    drop(buffer_b);
3846
3847    // Try opening the same buffer again as client B, and ensure we can
3848    // still do it despite the cancellation above.
3849    let buffer_b = project_b
3850        .update(cx_b, |p, cx| {
3851            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3852        })
3853        .await
3854        .unwrap();
3855
3856    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3857}
3858
3859#[gpui::test(iterations = 10)]
3860async fn test_leaving_project(
3861    executor: BackgroundExecutor,
3862    cx_a: &mut TestAppContext,
3863    cx_b: &mut TestAppContext,
3864    cx_c: &mut TestAppContext,
3865) {
3866    let mut server = TestServer::start(executor.clone()).await;
3867    let client_a = server.create_client(cx_a, "user_a").await;
3868    let client_b = server.create_client(cx_b, "user_b").await;
3869    let client_c = server.create_client(cx_c, "user_c").await;
3870    server
3871        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3872        .await;
3873    let active_call_a = cx_a.read(ActiveCall::global);
3874
3875    client_a
3876        .fs()
3877        .insert_tree(
3878            "/a",
3879            json!({
3880                "a.txt": "a-contents",
3881                "b.txt": "b-contents",
3882            }),
3883        )
3884        .await;
3885    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3886    let project_id = active_call_a
3887        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3888        .await
3889        .unwrap();
3890    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3891    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3892
3893    // Client A sees that a guest has joined.
3894    executor.run_until_parked();
3895
3896    project_a.read_with(cx_a, |project, _| {
3897        assert_eq!(project.collaborators().len(), 2);
3898    });
3899
3900    project_b1.read_with(cx_b, |project, _| {
3901        assert_eq!(project.collaborators().len(), 2);
3902    });
3903
3904    project_c.read_with(cx_c, |project, _| {
3905        assert_eq!(project.collaborators().len(), 2);
3906    });
3907
3908    // Client B opens a buffer.
3909    let buffer_b1 = project_b1
3910        .update(cx_b, |project, cx| {
3911            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3912            project.open_buffer((worktree_id, "a.txt"), cx)
3913        })
3914        .await
3915        .unwrap();
3916
3917    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3918
3919    // Drop client B's project and ensure client A and client C observe client B leaving.
3920    cx_b.update(|_| drop(project_b1));
3921    executor.run_until_parked();
3922
3923    project_a.read_with(cx_a, |project, _| {
3924        assert_eq!(project.collaborators().len(), 1);
3925    });
3926
3927    project_c.read_with(cx_c, |project, _| {
3928        assert_eq!(project.collaborators().len(), 1);
3929    });
3930
3931    // Client B re-joins the project and can open buffers as before.
3932    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3933    executor.run_until_parked();
3934
3935    project_a.read_with(cx_a, |project, _| {
3936        assert_eq!(project.collaborators().len(), 2);
3937    });
3938
3939    project_b2.read_with(cx_b, |project, _| {
3940        assert_eq!(project.collaborators().len(), 2);
3941    });
3942
3943    project_c.read_with(cx_c, |project, _| {
3944        assert_eq!(project.collaborators().len(), 2);
3945    });
3946
3947    let buffer_b2 = project_b2
3948        .update(cx_b, |project, cx| {
3949            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3950            project.open_buffer((worktree_id, "a.txt"), cx)
3951        })
3952        .await
3953        .unwrap();
3954
3955    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3956
3957    project_a.read_with(cx_a, |project, _| {
3958        assert_eq!(project.collaborators().len(), 2);
3959    });
3960
3961    // Drop client B's connection and ensure client A and client C observe client B leaving.
3962    client_b.disconnect(&cx_b.to_async());
3963    executor.advance_clock(RECONNECT_TIMEOUT);
3964
3965    project_a.read_with(cx_a, |project, _| {
3966        assert_eq!(project.collaborators().len(), 1);
3967    });
3968
3969    project_b2.read_with(cx_b, |project, cx| {
3970        assert!(project.is_disconnected(cx));
3971    });
3972
3973    project_c.read_with(cx_c, |project, _| {
3974        assert_eq!(project.collaborators().len(), 1);
3975    });
3976
3977    // Client B can't join the project, unless they re-join the room.
3978    cx_b.spawn(|cx| {
3979        Project::in_room(
3980            project_id,
3981            client_b.app_state.client.clone(),
3982            client_b.user_store().clone(),
3983            client_b.language_registry().clone(),
3984            FakeFs::new(cx.background_executor().clone()),
3985            cx,
3986        )
3987    })
3988    .await
3989    .unwrap_err();
3990
3991    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
3992    client_c.wait_for_current_user(cx_c).await;
3993    server.forbid_connections();
3994    server.disconnect_client(client_c.peer_id().unwrap());
3995    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
3996    executor.run_until_parked();
3997
3998    project_a.read_with(cx_a, |project, _| {
3999        assert_eq!(project.collaborators().len(), 0);
4000    });
4001
4002    project_b2.read_with(cx_b, |project, cx| {
4003        assert!(project.is_disconnected(cx));
4004    });
4005
4006    project_c.read_with(cx_c, |project, cx| {
4007        assert!(project.is_disconnected(cx));
4008    });
4009}
4010
4011#[gpui::test(iterations = 10)]
4012async fn test_collaborating_with_diagnostics(
4013    executor: BackgroundExecutor,
4014    cx_a: &mut TestAppContext,
4015    cx_b: &mut TestAppContext,
4016    cx_c: &mut TestAppContext,
4017) {
4018    let mut server = TestServer::start(executor.clone()).await;
4019    let client_a = server.create_client(cx_a, "user_a").await;
4020    let client_b = server.create_client(cx_b, "user_b").await;
4021    let client_c = server.create_client(cx_c, "user_c").await;
4022    server
4023        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4024        .await;
4025    let active_call_a = cx_a.read(ActiveCall::global);
4026
4027    client_a.language_registry().add(Arc::new(Language::new(
4028        LanguageConfig {
4029            name: "Rust".into(),
4030            matcher: LanguageMatcher {
4031                path_suffixes: vec!["rs".to_string()],
4032                ..Default::default()
4033            },
4034            ..Default::default()
4035        },
4036        Some(tree_sitter_rust::LANGUAGE.into()),
4037    )));
4038    let mut fake_language_servers = client_a
4039        .language_registry()
4040        .register_fake_lsp("Rust", Default::default());
4041
4042    // Share a project as client A
4043    client_a
4044        .fs()
4045        .insert_tree(
4046            path!("/a"),
4047            json!({
4048                "a.rs": "let one = two",
4049                "other.rs": "",
4050            }),
4051        )
4052        .await;
4053    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4054
4055    // Cause the language server to start.
4056    let _buffer = project_a
4057        .update(cx_a, |project, cx| {
4058            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4059        })
4060        .await
4061        .unwrap();
4062
4063    // Simulate a language server reporting errors for a file.
4064    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4065    fake_language_server
4066        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4067        .await;
4068    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4069        &lsp::PublishDiagnosticsParams {
4070            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4071            version: None,
4072            diagnostics: vec![lsp::Diagnostic {
4073                severity: Some(lsp::DiagnosticSeverity::WARNING),
4074                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4075                message: "message 0".to_string(),
4076                ..Default::default()
4077            }],
4078        },
4079    );
4080
4081    // Client A shares the project and, simultaneously, the language server
4082    // publishes a diagnostic. This is done to ensure that the server always
4083    // observes the latest diagnostics for a worktree.
4084    let project_id = active_call_a
4085        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4086        .await
4087        .unwrap();
4088    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4089        &lsp::PublishDiagnosticsParams {
4090            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4091            version: None,
4092            diagnostics: vec![lsp::Diagnostic {
4093                severity: Some(lsp::DiagnosticSeverity::ERROR),
4094                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4095                message: "message 1".to_string(),
4096                ..Default::default()
4097            }],
4098        },
4099    );
4100
4101    // Join the worktree as client B.
4102    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4103
4104    // Wait for server to see the diagnostics update.
4105    executor.run_until_parked();
4106
4107    // Ensure client B observes the new diagnostics.
4108
4109    project_b.read_with(cx_b, |project, cx| {
4110        assert_eq!(
4111            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4112            &[(
4113                ProjectPath {
4114                    worktree_id,
4115                    path: Arc::from(Path::new("a.rs")),
4116                },
4117                LanguageServerId(0),
4118                DiagnosticSummary {
4119                    error_count: 1,
4120                    warning_count: 0,
4121                },
4122            )]
4123        )
4124    });
4125
4126    // Join project as client C and observe the diagnostics.
4127    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4128    executor.run_until_parked();
4129    let project_c_diagnostic_summaries =
4130        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4131            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4132        })));
4133    project_c.update(cx_c, |_, cx| {
4134        let summaries = project_c_diagnostic_summaries.clone();
4135        cx.subscribe(&project_c, {
4136            move |p, _, event, cx| {
4137                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4138                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4139                }
4140            }
4141        })
4142        .detach();
4143    });
4144
4145    executor.run_until_parked();
4146    assert_eq!(
4147        project_c_diagnostic_summaries.borrow().as_slice(),
4148        &[(
4149            ProjectPath {
4150                worktree_id,
4151                path: Arc::from(Path::new("a.rs")),
4152            },
4153            LanguageServerId(0),
4154            DiagnosticSummary {
4155                error_count: 1,
4156                warning_count: 0,
4157            },
4158        )]
4159    );
4160
4161    // Simulate a language server reporting more errors for a file.
4162    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4163        &lsp::PublishDiagnosticsParams {
4164            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4165            version: None,
4166            diagnostics: vec![
4167                lsp::Diagnostic {
4168                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4169                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4170                    message: "message 1".to_string(),
4171                    ..Default::default()
4172                },
4173                lsp::Diagnostic {
4174                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4175                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4176                    message: "message 2".to_string(),
4177                    ..Default::default()
4178                },
4179            ],
4180        },
4181    );
4182
4183    // Clients B and C get the updated summaries
4184    executor.run_until_parked();
4185
4186    project_b.read_with(cx_b, |project, cx| {
4187        assert_eq!(
4188            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4189            [(
4190                ProjectPath {
4191                    worktree_id,
4192                    path: Arc::from(Path::new("a.rs")),
4193                },
4194                LanguageServerId(0),
4195                DiagnosticSummary {
4196                    error_count: 1,
4197                    warning_count: 1,
4198                },
4199            )]
4200        );
4201    });
4202
4203    project_c.read_with(cx_c, |project, cx| {
4204        assert_eq!(
4205            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4206            [(
4207                ProjectPath {
4208                    worktree_id,
4209                    path: Arc::from(Path::new("a.rs")),
4210                },
4211                LanguageServerId(0),
4212                DiagnosticSummary {
4213                    error_count: 1,
4214                    warning_count: 1,
4215                },
4216            )]
4217        );
4218    });
4219
4220    // Open the file with the errors on client B. They should be present.
4221    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4222    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4223
4224    buffer_b.read_with(cx_b, |buffer, _| {
4225        assert_eq!(
4226            buffer
4227                .snapshot()
4228                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4229                .collect::<Vec<_>>(),
4230            &[
4231                DiagnosticEntry {
4232                    range: Point::new(0, 4)..Point::new(0, 7),
4233                    diagnostic: Diagnostic {
4234                        group_id: 2,
4235                        message: "message 1".to_string(),
4236                        severity: lsp::DiagnosticSeverity::ERROR,
4237                        is_primary: true,
4238                        source_kind: DiagnosticSourceKind::Pushed,
4239                        ..Diagnostic::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                        source_kind: DiagnosticSourceKind::Pushed,
4250                        ..Diagnostic::default()
4251                    }
4252                }
4253            ]
4254        );
4255    });
4256
4257    // Simulate a language server reporting no errors for a file.
4258    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4259        &lsp::PublishDiagnosticsParams {
4260            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4261            version: None,
4262            diagnostics: Vec::new(),
4263        },
4264    );
4265    executor.run_until_parked();
4266
4267    project_a.read_with(cx_a, |project, cx| {
4268        assert_eq!(
4269            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4270            []
4271        )
4272    });
4273
4274    project_b.read_with(cx_b, |project, cx| {
4275        assert_eq!(
4276            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4277            []
4278        )
4279    });
4280
4281    project_c.read_with(cx_c, |project, cx| {
4282        assert_eq!(
4283            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4284            []
4285        )
4286    });
4287}
4288
4289#[gpui::test(iterations = 10)]
4290async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4291    executor: BackgroundExecutor,
4292    cx_a: &mut TestAppContext,
4293    cx_b: &mut TestAppContext,
4294) {
4295    let mut server = TestServer::start(executor.clone()).await;
4296    let client_a = server.create_client(cx_a, "user_a").await;
4297    let client_b = server.create_client(cx_b, "user_b").await;
4298    server
4299        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4300        .await;
4301
4302    client_a.language_registry().add(rust_lang());
4303    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4304        "Rust",
4305        FakeLspAdapter {
4306            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4307            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4308            ..Default::default()
4309        },
4310    );
4311
4312    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4313    client_a
4314        .fs()
4315        .insert_tree(
4316            path!("/test"),
4317            json!({
4318                "one.rs": "const ONE: usize = 1;",
4319                "two.rs": "const TWO: usize = 2;",
4320                "three.rs": "const THREE: usize = 3;",
4321                "four.rs": "const FOUR: usize = 3;",
4322                "five.rs": "const FIVE: usize = 3;",
4323            }),
4324        )
4325        .await;
4326
4327    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4328
4329    // Share a project as client A
4330    let active_call_a = cx_a.read(ActiveCall::global);
4331    let project_id = active_call_a
4332        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4333        .await
4334        .unwrap();
4335
4336    // Join the project as client B and open all three files.
4337    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4338    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4339        project_b.update(cx_b, |p, cx| {
4340            p.open_buffer_with_lsp((worktree_id, file_name), cx)
4341        })
4342    }))
4343    .await
4344    .unwrap();
4345
4346    // Simulate a language server reporting errors for a file.
4347    let fake_language_server = fake_language_servers.next().await.unwrap();
4348    fake_language_server
4349        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
4350            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4351        })
4352        .await
4353        .into_response()
4354        .unwrap();
4355    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4356        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4357        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4358            lsp::WorkDoneProgressBegin {
4359                title: "Progress Began".into(),
4360                ..Default::default()
4361            },
4362        )),
4363    });
4364    for file_name in file_names {
4365        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4366            &lsp::PublishDiagnosticsParams {
4367                uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4368                version: None,
4369                diagnostics: vec![lsp::Diagnostic {
4370                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4371                    source: Some("the-disk-based-diagnostics-source".into()),
4372                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4373                    message: "message one".to_string(),
4374                    ..Default::default()
4375                }],
4376            },
4377        );
4378    }
4379    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4380        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4381        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4382            lsp::WorkDoneProgressEnd { message: None },
4383        )),
4384    });
4385
4386    // When the "disk base diagnostics finished" message is received, the buffers'
4387    // diagnostics are expected to be present.
4388    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4389    project_b.update(cx_b, {
4390        let project_b = project_b.clone();
4391        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4392        move |_, cx| {
4393            cx.subscribe(&project_b, move |_, _, event, cx| {
4394                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4395                    disk_based_diagnostics_finished.store(true, SeqCst);
4396                    for (buffer, _) in &guest_buffers {
4397                        assert_eq!(
4398                            buffer
4399                                .read(cx)
4400                                .snapshot()
4401                                .diagnostics_in_range::<_, usize>(0..5, false)
4402                                .count(),
4403                            1,
4404                            "expected a diagnostic for buffer {:?}",
4405                            buffer.read(cx).file().unwrap().path(),
4406                        );
4407                    }
4408                }
4409            })
4410            .detach();
4411        }
4412    });
4413
4414    executor.run_until_parked();
4415    assert!(disk_based_diagnostics_finished.load(SeqCst));
4416}
4417
4418#[gpui::test(iterations = 10)]
4419async fn test_reloading_buffer_manually(
4420    executor: BackgroundExecutor,
4421    cx_a: &mut TestAppContext,
4422    cx_b: &mut TestAppContext,
4423) {
4424    let mut server = TestServer::start(executor.clone()).await;
4425    let client_a = server.create_client(cx_a, "user_a").await;
4426    let client_b = server.create_client(cx_b, "user_b").await;
4427    server
4428        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4429        .await;
4430    let active_call_a = cx_a.read(ActiveCall::global);
4431
4432    client_a
4433        .fs()
4434        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4435        .await;
4436    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4437    let buffer_a = project_a
4438        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4439        .await
4440        .unwrap();
4441    let project_id = active_call_a
4442        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4443        .await
4444        .unwrap();
4445
4446    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4447
4448    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4449    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4450    buffer_b.update(cx_b, |buffer, cx| {
4451        buffer.edit([(4..7, "six")], None, cx);
4452        buffer.edit([(10..11, "6")], None, cx);
4453        assert_eq!(buffer.text(), "let six = 6;");
4454        assert!(buffer.is_dirty());
4455        assert!(!buffer.has_conflict());
4456    });
4457    executor.run_until_parked();
4458
4459    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4460
4461    client_a
4462        .fs()
4463        .save(
4464            path!("/a/a.rs").as_ref(),
4465            &Rope::from("let seven = 7;"),
4466            LineEnding::Unix,
4467        )
4468        .await
4469        .unwrap();
4470    executor.run_until_parked();
4471
4472    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4473
4474    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4475
4476    project_b
4477        .update(cx_b, |project, cx| {
4478            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4479        })
4480        .await
4481        .unwrap();
4482
4483    buffer_a.read_with(cx_a, |buffer, _| {
4484        assert_eq!(buffer.text(), "let seven = 7;");
4485        assert!(!buffer.is_dirty());
4486        assert!(!buffer.has_conflict());
4487    });
4488
4489    buffer_b.read_with(cx_b, |buffer, _| {
4490        assert_eq!(buffer.text(), "let seven = 7;");
4491        assert!(!buffer.is_dirty());
4492        assert!(!buffer.has_conflict());
4493    });
4494
4495    buffer_a.update(cx_a, |buffer, cx| {
4496        // Undoing on the host is a no-op when the reload was initiated by the guest.
4497        buffer.undo(cx);
4498        assert_eq!(buffer.text(), "let seven = 7;");
4499        assert!(!buffer.is_dirty());
4500        assert!(!buffer.has_conflict());
4501    });
4502    buffer_b.update(cx_b, |buffer, cx| {
4503        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4504        buffer.undo(cx);
4505        assert_eq!(buffer.text(), "let six = 6;");
4506        assert!(buffer.is_dirty());
4507        assert!(!buffer.has_conflict());
4508    });
4509}
4510
4511#[gpui::test(iterations = 10)]
4512async fn test_formatting_buffer(
4513    executor: BackgroundExecutor,
4514    cx_a: &mut TestAppContext,
4515    cx_b: &mut TestAppContext,
4516) {
4517    let mut server = TestServer::start(executor.clone()).await;
4518    let client_a = server.create_client(cx_a, "user_a").await;
4519    let client_b = server.create_client(cx_b, "user_b").await;
4520    server
4521        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4522        .await;
4523    let active_call_a = cx_a.read(ActiveCall::global);
4524
4525    client_a.language_registry().add(rust_lang());
4526    let mut fake_language_servers = client_a
4527        .language_registry()
4528        .register_fake_lsp("Rust", FakeLspAdapter::default());
4529
4530    // Here we insert a fake tree with a directory that exists on disk. This is needed
4531    // because later we'll invoke a command, which requires passing a working directory
4532    // that points to a valid location on disk.
4533    let directory = env::current_dir().unwrap();
4534    client_a
4535        .fs()
4536        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4537        .await;
4538    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4539    let project_id = active_call_a
4540        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4541        .await
4542        .unwrap();
4543    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4544
4545    let buffer_b = project_b
4546        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4547        .await
4548        .unwrap();
4549
4550    let _handle = project_b.update(cx_b, |project, cx| {
4551        project.register_buffer_with_language_servers(&buffer_b, cx)
4552    });
4553    let fake_language_server = fake_language_servers.next().await.unwrap();
4554    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4555        Ok(Some(vec![
4556            lsp::TextEdit {
4557                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4558                new_text: "h".to_string(),
4559            },
4560            lsp::TextEdit {
4561                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4562                new_text: "y".to_string(),
4563            },
4564        ]))
4565    });
4566
4567    project_b
4568        .update(cx_b, |project, cx| {
4569            project.format(
4570                HashSet::from_iter([buffer_b.clone()]),
4571                LspFormatTarget::Buffers,
4572                true,
4573                FormatTrigger::Save,
4574                cx,
4575            )
4576        })
4577        .await
4578        .unwrap();
4579
4580    // The edits from the LSP are applied, and a final newline is added.
4581    assert_eq!(
4582        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4583        "let honey = \"two\"\n"
4584    );
4585
4586    // There is no `awk` command on Windows.
4587    #[cfg(not(target_os = "windows"))]
4588    {
4589        // Ensure buffer can be formatted using an external command. Notice how the
4590        // host's configuration is honored as opposed to using the guest's settings.
4591        cx_a.update(|cx| {
4592            SettingsStore::update_global(cx, |store, cx| {
4593                store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4594                    file.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
4595                        Formatter::External {
4596                            command: "awk".into(),
4597                            arguments: Some(
4598                                vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
4599                            ),
4600                        },
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::Single(
4702                    Formatter::LanguageServer { name: None },
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.definitions(&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.definitions(&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_definitions(&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    assert_eq!(references.await.unwrap(), []);
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.definitions(&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.definitions(&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}