integration_tests.rs

   1use crate::{
   2    rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
   3    tests::{
   4        RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
   5        room_participants, rust_lang,
   6    },
   7};
   8use anyhow::{Result, anyhow};
   9use assistant_context_editor::ContextStore;
  10use assistant_slash_command::SlashCommandWorkingSet;
  11use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, assert_hunks};
  12use call::{ActiveCall, ParticipantLocation, Room, room};
  13use client::{RECEIVE_TIMEOUT, User};
  14use collections::{HashMap, HashSet};
  15use fs::{FakeFs, Fs as _, RemoveOptions};
  16use futures::{StreamExt as _, channel::mpsc};
  17use git::status::{FileStatus, StatusCode, TrackedStatus, UnmergedStatus, UnmergedStatusCode};
  18use gpui::{
  19    App, BackgroundExecutor, Entity, Modifiers, MouseButton, MouseDownEvent, TestAppContext,
  20    UpdateGlobal, px, size,
  21};
  22use language::{
  23    Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher,
  24    LineEnding, OffsetRangeExt, Point, Rope,
  25    language_settings::{
  26        AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
  27    },
  28    tree_sitter_rust, tree_sitter_typescript,
  29};
  30use lsp::{LanguageServerId, OneOf};
  31use parking_lot::Mutex;
  32use pretty_assertions::assert_eq;
  33use project::{
  34    DiagnosticSummary, HoverBlockKind, Project, ProjectPath,
  35    lsp_store::{FormatTrigger, LspFormatTarget},
  36    search::{SearchQuery, SearchResult},
  37};
  38use prompt_store::PromptBuilder;
  39use rand::prelude::*;
  40use serde_json::json;
  41use settings::SettingsStore;
  42use std::{
  43    cell::{Cell, RefCell},
  44    env, future, mem,
  45    path::{Path, PathBuf},
  46    rc::Rc,
  47    sync::{
  48        Arc,
  49        atomic::{AtomicBool, Ordering::SeqCst},
  50    },
  51    time::Duration,
  52};
  53use unindent::Unindent as _;
  54use util::{path, separator, uri};
  55use workspace::Pane;
  56
  57#[ctor::ctor]
  58fn init_logger() {
  59    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                separator!("a.txt"),
1680                separator!("b.txt"),
1681                separator!("subdir2"),
1682                separator!("subdir2/f.txt"),
1683                separator!("subdir2/g.txt"),
1684                separator!("subdir2/h.txt"),
1685                separator!("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                separator!("a.txt"),
1713                separator!("b.txt"),
1714                separator!("subdir2"),
1715                separator!("subdir2/f.txt"),
1716                separator!("subdir2/g.txt"),
1717                separator!("subdir2/h.txt"),
1718                separator!("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                separator!("a.txt"),
1810                separator!("b.txt"),
1811                separator!("subdir2"),
1812                separator!("subdir2/f.txt"),
1813                separator!("subdir2/g.txt"),
1814                separator!("subdir2/h.txt"),
1815                separator!("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    );
2626
2627    // Create the buffer
2628    let buffer_local_a = project_local
2629        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2630        .await
2631        .unwrap();
2632    let local_unstaged_diff_a = project_local
2633        .update(cx_a, |p, cx| {
2634            p.open_unstaged_diff(buffer_local_a.clone(), cx)
2635        })
2636        .await
2637        .unwrap();
2638
2639    // Wait for it to catch up to the new diff
2640    executor.run_until_parked();
2641    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2642        let buffer = buffer_local_a.read(cx);
2643        assert_eq!(
2644            diff.base_text_string().as_deref(),
2645            Some(staged_text.as_str())
2646        );
2647        assert_hunks(
2648            diff.hunks_in_row_range(0..4, buffer, cx),
2649            buffer,
2650            &diff.base_text_string().unwrap(),
2651            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2652        );
2653    });
2654
2655    // Create remote buffer
2656    let remote_buffer_a = project_remote
2657        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
2658        .await
2659        .unwrap();
2660    let remote_unstaged_diff_a = project_remote
2661        .update(cx_b, |p, cx| {
2662            p.open_unstaged_diff(remote_buffer_a.clone(), cx)
2663        })
2664        .await
2665        .unwrap();
2666
2667    // Wait remote buffer to catch up to the new diff
2668    executor.run_until_parked();
2669    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2670        let buffer = remote_buffer_a.read(cx);
2671        assert_eq!(
2672            diff.base_text_string().as_deref(),
2673            Some(staged_text.as_str())
2674        );
2675        assert_hunks(
2676            diff.hunks_in_row_range(0..4, buffer, cx),
2677            buffer,
2678            &diff.base_text_string().unwrap(),
2679            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2680        );
2681    });
2682
2683    // Open uncommitted changes on the guest, without opening them on the host first
2684    let remote_uncommitted_diff_a = project_remote
2685        .update(cx_b, |p, cx| {
2686            p.open_uncommitted_diff(remote_buffer_a.clone(), cx)
2687        })
2688        .await
2689        .unwrap();
2690    executor.run_until_parked();
2691    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2692        let buffer = remote_buffer_a.read(cx);
2693        assert_eq!(
2694            diff.base_text_string().as_deref(),
2695            Some(committed_text.as_str())
2696        );
2697        assert_hunks(
2698            diff.hunks_in_row_range(0..4, buffer, cx),
2699            buffer,
2700            &diff.base_text_string().unwrap(),
2701            &[(
2702                1..2,
2703                "TWO\n",
2704                "two\n",
2705                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2706            )],
2707        );
2708    });
2709
2710    // Update the index text of the open buffer
2711    client_a.fs().set_index_for_repo(
2712        Path::new("/dir/.git"),
2713        &[("a.txt".into(), new_staged_text.clone())],
2714    );
2715    client_a.fs().set_head_for_repo(
2716        Path::new("/dir/.git"),
2717        &[("a.txt".into(), new_committed_text.clone())],
2718    );
2719
2720    // Wait for buffer_local_a to receive it
2721    executor.run_until_parked();
2722    local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
2723        let buffer = buffer_local_a.read(cx);
2724        assert_eq!(
2725            diff.base_text_string().as_deref(),
2726            Some(new_staged_text.as_str())
2727        );
2728        assert_hunks(
2729            diff.hunks_in_row_range(0..4, buffer, cx),
2730            buffer,
2731            &diff.base_text_string().unwrap(),
2732            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2733        );
2734    });
2735
2736    // Guest receives index text update
2737    remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
2738        let buffer = remote_buffer_a.read(cx);
2739        assert_eq!(
2740            diff.base_text_string().as_deref(),
2741            Some(new_staged_text.as_str())
2742        );
2743        assert_hunks(
2744            diff.hunks_in_row_range(0..4, buffer, cx),
2745            buffer,
2746            &diff.base_text_string().unwrap(),
2747            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2748        );
2749    });
2750
2751    remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
2752        let buffer = remote_buffer_a.read(cx);
2753        assert_eq!(
2754            diff.base_text_string().as_deref(),
2755            Some(new_committed_text.as_str())
2756        );
2757        assert_hunks(
2758            diff.hunks_in_row_range(0..4, buffer, cx),
2759            buffer,
2760            &diff.base_text_string().unwrap(),
2761            &[(
2762                1..2,
2763                "TWO_HUNDRED\n",
2764                "two\n",
2765                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2766            )],
2767        );
2768    });
2769
2770    // Nested git dir
2771    let staged_text = "
2772        one
2773        three
2774    "
2775    .unindent();
2776
2777    let new_staged_text = "
2778        one
2779        two
2780    "
2781    .unindent();
2782
2783    client_a.fs().set_index_for_repo(
2784        Path::new("/dir/sub/.git"),
2785        &[("b.txt".into(), staged_text.clone())],
2786    );
2787
2788    // Create the buffer
2789    let buffer_local_b = project_local
2790        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2791        .await
2792        .unwrap();
2793    let local_unstaged_diff_b = project_local
2794        .update(cx_a, |p, cx| {
2795            p.open_unstaged_diff(buffer_local_b.clone(), cx)
2796        })
2797        .await
2798        .unwrap();
2799
2800    // Wait for it to catch up to the new diff
2801    executor.run_until_parked();
2802    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2803        let buffer = buffer_local_b.read(cx);
2804        assert_eq!(
2805            diff.base_text_string().as_deref(),
2806            Some(staged_text.as_str())
2807        );
2808        assert_hunks(
2809            diff.hunks_in_row_range(0..4, buffer, cx),
2810            buffer,
2811            &diff.base_text_string().unwrap(),
2812            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2813        );
2814    });
2815
2816    // Create remote buffer
2817    let remote_buffer_b = project_remote
2818        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx))
2819        .await
2820        .unwrap();
2821    let remote_unstaged_diff_b = project_remote
2822        .update(cx_b, |p, cx| {
2823            p.open_unstaged_diff(remote_buffer_b.clone(), cx)
2824        })
2825        .await
2826        .unwrap();
2827
2828    executor.run_until_parked();
2829    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2830        let buffer = remote_buffer_b.read(cx);
2831        assert_eq!(
2832            diff.base_text_string().as_deref(),
2833            Some(staged_text.as_str())
2834        );
2835        assert_hunks(
2836            diff.hunks_in_row_range(0..4, buffer, cx),
2837            buffer,
2838            &staged_text,
2839            &[(1..2, "", "two\n", DiffHunkStatus::added_none())],
2840        );
2841    });
2842
2843    // Updatet the staged text
2844    client_a.fs().set_index_for_repo(
2845        Path::new("/dir/sub/.git"),
2846        &[("b.txt".into(), new_staged_text.clone())],
2847    );
2848
2849    // Wait for buffer_local_b to receive it
2850    executor.run_until_parked();
2851    local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
2852        let buffer = buffer_local_b.read(cx);
2853        assert_eq!(
2854            diff.base_text_string().as_deref(),
2855            Some(new_staged_text.as_str())
2856        );
2857        assert_hunks(
2858            diff.hunks_in_row_range(0..4, buffer, cx),
2859            buffer,
2860            &new_staged_text,
2861            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2862        );
2863    });
2864
2865    remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
2866        let buffer = remote_buffer_b.read(cx);
2867        assert_eq!(
2868            diff.base_text_string().as_deref(),
2869            Some(new_staged_text.as_str())
2870        );
2871        assert_hunks(
2872            diff.hunks_in_row_range(0..4, buffer, cx),
2873            buffer,
2874            &new_staged_text,
2875            &[(2..3, "", "three\n", DiffHunkStatus::added_none())],
2876        );
2877    });
2878}
2879
2880#[gpui::test(iterations = 10)]
2881async fn test_git_branch_name(
2882    executor: BackgroundExecutor,
2883    cx_a: &mut TestAppContext,
2884    cx_b: &mut TestAppContext,
2885    cx_c: &mut TestAppContext,
2886) {
2887    let mut server = TestServer::start(executor.clone()).await;
2888    let client_a = server.create_client(cx_a, "user_a").await;
2889    let client_b = server.create_client(cx_b, "user_b").await;
2890    let client_c = server.create_client(cx_c, "user_c").await;
2891    server
2892        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2893        .await;
2894    let active_call_a = cx_a.read(ActiveCall::global);
2895
2896    client_a
2897        .fs()
2898        .insert_tree(
2899            "/dir",
2900            json!({
2901            ".git": {},
2902            }),
2903        )
2904        .await;
2905
2906    let (project_local, _worktree_id) = client_a.build_local_project("/dir", cx_a).await;
2907    let project_id = active_call_a
2908        .update(cx_a, |call, cx| {
2909            call.share_project(project_local.clone(), cx)
2910        })
2911        .await
2912        .unwrap();
2913
2914    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
2915    client_a
2916        .fs()
2917        .set_branch_name(Path::new("/dir/.git"), Some("branch-1"));
2918
2919    // Wait for it to catch up to the new branch
2920    executor.run_until_parked();
2921
2922    #[track_caller]
2923    fn assert_branch(branch_name: Option<impl Into<String>>, project: &Project, cx: &App) {
2924        let branch_name = branch_name.map(Into::into);
2925        let repositories = project.repositories(cx).values().collect::<Vec<_>>();
2926        assert_eq!(repositories.len(), 1);
2927        let repository = repositories[0].clone();
2928        assert_eq!(
2929            repository
2930                .read(cx)
2931                .branch
2932                .as_ref()
2933                .map(|branch| branch.name().to_owned()),
2934            branch_name
2935        )
2936    }
2937
2938    // Smoke test branch reading
2939
2940    project_local.read_with(cx_a, |project, cx| {
2941        assert_branch(Some("branch-1"), project, cx)
2942    });
2943
2944    project_remote.read_with(cx_b, |project, cx| {
2945        assert_branch(Some("branch-1"), project, cx)
2946    });
2947
2948    client_a
2949        .fs()
2950        .set_branch_name(Path::new("/dir/.git"), Some("branch-2"));
2951
2952    // Wait for buffer_local_a to receive it
2953    executor.run_until_parked();
2954
2955    // Smoke test branch reading
2956
2957    project_local.read_with(cx_a, |project, cx| {
2958        assert_branch(Some("branch-2"), project, cx)
2959    });
2960
2961    project_remote.read_with(cx_b, |project, cx| {
2962        assert_branch(Some("branch-2"), project, cx)
2963    });
2964
2965    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
2966    executor.run_until_parked();
2967
2968    project_remote_c.read_with(cx_c, |project, cx| {
2969        assert_branch(Some("branch-2"), project, cx)
2970    });
2971}
2972
2973#[gpui::test]
2974async fn test_git_status_sync(
2975    executor: BackgroundExecutor,
2976    cx_a: &mut TestAppContext,
2977    cx_b: &mut TestAppContext,
2978    cx_c: &mut TestAppContext,
2979) {
2980    let mut server = TestServer::start(executor.clone()).await;
2981    let client_a = server.create_client(cx_a, "user_a").await;
2982    let client_b = server.create_client(cx_b, "user_b").await;
2983    let client_c = server.create_client(cx_c, "user_c").await;
2984    server
2985        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
2986        .await;
2987    let active_call_a = cx_a.read(ActiveCall::global);
2988
2989    client_a
2990        .fs()
2991        .insert_tree(
2992            path!("/dir"),
2993            json!({
2994                ".git": {},
2995                "a.txt": "a",
2996                "b.txt": "b",
2997                "c.txt": "c",
2998            }),
2999        )
3000        .await;
3001
3002    // Initially, a.txt is uncommitted, but present in the index,
3003    // and b.txt is unmerged.
3004    client_a.fs().set_head_for_repo(
3005        path!("/dir/.git").as_ref(),
3006        &[("b.txt".into(), "B".into()), ("c.txt".into(), "c".into())],
3007    );
3008    client_a.fs().set_index_for_repo(
3009        path!("/dir/.git").as_ref(),
3010        &[
3011            ("a.txt".into(), "".into()),
3012            ("b.txt".into(), "B".into()),
3013            ("c.txt".into(), "c".into()),
3014        ],
3015    );
3016    client_a.fs().set_unmerged_paths_for_repo(
3017        path!("/dir/.git").as_ref(),
3018        &[(
3019            "b.txt".into(),
3020            UnmergedStatus {
3021                first_head: UnmergedStatusCode::Updated,
3022                second_head: UnmergedStatusCode::Deleted,
3023            },
3024        )],
3025    );
3026
3027    const A_STATUS_START: FileStatus = FileStatus::Tracked(TrackedStatus {
3028        index_status: StatusCode::Added,
3029        worktree_status: StatusCode::Modified,
3030    });
3031    const B_STATUS_START: FileStatus = FileStatus::Unmerged(UnmergedStatus {
3032        first_head: UnmergedStatusCode::Updated,
3033        second_head: UnmergedStatusCode::Deleted,
3034    });
3035
3036    let (project_local, _worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3037    let project_id = active_call_a
3038        .update(cx_a, |call, cx| {
3039            call.share_project(project_local.clone(), cx)
3040        })
3041        .await
3042        .unwrap();
3043
3044    let project_remote = client_b.join_remote_project(project_id, cx_b).await;
3045
3046    // Wait for it to catch up to the new status
3047    executor.run_until_parked();
3048
3049    #[track_caller]
3050    fn assert_status(
3051        file: impl AsRef<Path>,
3052        status: Option<FileStatus>,
3053        project: &Project,
3054        cx: &App,
3055    ) {
3056        let file = file.as_ref();
3057        let repos = project
3058            .repositories(cx)
3059            .values()
3060            .cloned()
3061            .collect::<Vec<_>>();
3062        assert_eq!(repos.len(), 1);
3063        let repo = repos.into_iter().next().unwrap();
3064        assert_eq!(
3065            repo.read(cx)
3066                .status_for_path(&file.into())
3067                .map(|entry| entry.status),
3068            status
3069        );
3070    }
3071
3072    project_local.read_with(cx_a, |project, cx| {
3073        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3074        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3075        assert_status("c.txt", None, project, cx);
3076    });
3077
3078    project_remote.read_with(cx_b, |project, cx| {
3079        assert_status("a.txt", Some(A_STATUS_START), project, cx);
3080        assert_status("b.txt", Some(B_STATUS_START), project, cx);
3081        assert_status("c.txt", None, project, cx);
3082    });
3083
3084    const A_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3085        index_status: StatusCode::Added,
3086        worktree_status: StatusCode::Unmodified,
3087    });
3088    const B_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3089        index_status: StatusCode::Deleted,
3090        worktree_status: StatusCode::Added,
3091    });
3092    const C_STATUS_END: FileStatus = FileStatus::Tracked(TrackedStatus {
3093        index_status: StatusCode::Unmodified,
3094        worktree_status: StatusCode::Modified,
3095    });
3096
3097    // Delete b.txt from the index, mark conflict as resolved,
3098    // and modify c.txt in the working copy.
3099    client_a.fs().set_index_for_repo(
3100        path!("/dir/.git").as_ref(),
3101        &[("a.txt".into(), "a".into()), ("c.txt".into(), "c".into())],
3102    );
3103    client_a
3104        .fs()
3105        .set_unmerged_paths_for_repo(path!("/dir/.git").as_ref(), &[]);
3106    client_a
3107        .fs()
3108        .atomic_write(path!("/dir/c.txt").into(), "CC".into())
3109        .await
3110        .unwrap();
3111
3112    // Wait for buffer_local_a to receive it
3113    executor.run_until_parked();
3114
3115    // Smoke test status reading
3116    project_local.read_with(cx_a, |project, cx| {
3117        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3118        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3119        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3120    });
3121
3122    project_remote.read_with(cx_b, |project, cx| {
3123        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3124        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3125        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3126    });
3127
3128    // And synchronization while joining
3129    let project_remote_c = client_c.join_remote_project(project_id, cx_c).await;
3130    executor.run_until_parked();
3131
3132    project_remote_c.read_with(cx_c, |project, cx| {
3133        assert_status("a.txt", Some(A_STATUS_END), project, cx);
3134        assert_status("b.txt", Some(B_STATUS_END), project, cx);
3135        assert_status("c.txt", Some(C_STATUS_END), project, cx);
3136    });
3137
3138    // Now remove the original git repository and check that collaborators are notified.
3139    client_a
3140        .fs()
3141        .remove_dir(path!("/dir/.git").as_ref(), RemoveOptions::default())
3142        .await
3143        .unwrap();
3144
3145    executor.run_until_parked();
3146    project_remote.update(cx_b, |project, cx| {
3147        pretty_assertions::assert_eq!(
3148            project.git_store().read(cx).repo_snapshots(cx),
3149            HashMap::default()
3150        );
3151    });
3152    project_remote_c.update(cx_c, |project, cx| {
3153        pretty_assertions::assert_eq!(
3154            project.git_store().read(cx).repo_snapshots(cx),
3155            HashMap::default()
3156        );
3157    });
3158}
3159
3160#[gpui::test(iterations = 10)]
3161async fn test_fs_operations(
3162    executor: BackgroundExecutor,
3163    cx_a: &mut TestAppContext,
3164    cx_b: &mut TestAppContext,
3165) {
3166    let mut server = TestServer::start(executor.clone()).await;
3167    let client_a = server.create_client(cx_a, "user_a").await;
3168    let client_b = server.create_client(cx_b, "user_b").await;
3169    server
3170        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3171        .await;
3172    let active_call_a = cx_a.read(ActiveCall::global);
3173
3174    client_a
3175        .fs()
3176        .insert_tree(
3177            path!("/dir"),
3178            json!({
3179                "a.txt": "a-contents",
3180                "b.txt": "b-contents",
3181            }),
3182        )
3183        .await;
3184    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3185    let project_id = active_call_a
3186        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3187        .await
3188        .unwrap();
3189    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3190
3191    let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
3192    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3193
3194    let entry = project_b
3195        .update(cx_b, |project, cx| {
3196            project.create_entry((worktree_id, "c.txt"), false, cx)
3197        })
3198        .await
3199        .unwrap()
3200        .to_included()
3201        .unwrap();
3202
3203    worktree_a.read_with(cx_a, |worktree, _| {
3204        assert_eq!(
3205            worktree
3206                .paths()
3207                .map(|p| p.to_string_lossy())
3208                .collect::<Vec<_>>(),
3209            ["a.txt", "b.txt", "c.txt"]
3210        );
3211    });
3212
3213    worktree_b.read_with(cx_b, |worktree, _| {
3214        assert_eq!(
3215            worktree
3216                .paths()
3217                .map(|p| p.to_string_lossy())
3218                .collect::<Vec<_>>(),
3219            ["a.txt", "b.txt", "c.txt"]
3220        );
3221    });
3222
3223    project_b
3224        .update(cx_b, |project, cx| {
3225            project.rename_entry(entry.id, Path::new("d.txt"), cx)
3226        })
3227        .await
3228        .unwrap()
3229        .to_included()
3230        .unwrap();
3231
3232    worktree_a.read_with(cx_a, |worktree, _| {
3233        assert_eq!(
3234            worktree
3235                .paths()
3236                .map(|p| p.to_string_lossy())
3237                .collect::<Vec<_>>(),
3238            ["a.txt", "b.txt", "d.txt"]
3239        );
3240    });
3241
3242    worktree_b.read_with(cx_b, |worktree, _| {
3243        assert_eq!(
3244            worktree
3245                .paths()
3246                .map(|p| p.to_string_lossy())
3247                .collect::<Vec<_>>(),
3248            ["a.txt", "b.txt", "d.txt"]
3249        );
3250    });
3251
3252    let dir_entry = project_b
3253        .update(cx_b, |project, cx| {
3254            project.create_entry((worktree_id, "DIR"), true, cx)
3255        })
3256        .await
3257        .unwrap()
3258        .to_included()
3259        .unwrap();
3260
3261    worktree_a.read_with(cx_a, |worktree, _| {
3262        assert_eq!(
3263            worktree
3264                .paths()
3265                .map(|p| p.to_string_lossy())
3266                .collect::<Vec<_>>(),
3267            ["DIR", "a.txt", "b.txt", "d.txt"]
3268        );
3269    });
3270
3271    worktree_b.read_with(cx_b, |worktree, _| {
3272        assert_eq!(
3273            worktree
3274                .paths()
3275                .map(|p| p.to_string_lossy())
3276                .collect::<Vec<_>>(),
3277            ["DIR", "a.txt", "b.txt", "d.txt"]
3278        );
3279    });
3280
3281    project_b
3282        .update(cx_b, |project, cx| {
3283            project.create_entry((worktree_id, "DIR/e.txt"), false, cx)
3284        })
3285        .await
3286        .unwrap()
3287        .to_included()
3288        .unwrap();
3289
3290    project_b
3291        .update(cx_b, |project, cx| {
3292            project.create_entry((worktree_id, "DIR/SUBDIR"), true, cx)
3293        })
3294        .await
3295        .unwrap()
3296        .to_included()
3297        .unwrap();
3298
3299    project_b
3300        .update(cx_b, |project, cx| {
3301            project.create_entry((worktree_id, "DIR/SUBDIR/f.txt"), false, cx)
3302        })
3303        .await
3304        .unwrap()
3305        .to_included()
3306        .unwrap();
3307
3308    worktree_a.read_with(cx_a, |worktree, _| {
3309        assert_eq!(
3310            worktree
3311                .paths()
3312                .map(|p| p.to_string_lossy())
3313                .collect::<Vec<_>>(),
3314            [
3315                separator!("DIR"),
3316                separator!("DIR/SUBDIR"),
3317                separator!("DIR/SUBDIR/f.txt"),
3318                separator!("DIR/e.txt"),
3319                separator!("a.txt"),
3320                separator!("b.txt"),
3321                separator!("d.txt")
3322            ]
3323        );
3324    });
3325
3326    worktree_b.read_with(cx_b, |worktree, _| {
3327        assert_eq!(
3328            worktree
3329                .paths()
3330                .map(|p| p.to_string_lossy())
3331                .collect::<Vec<_>>(),
3332            [
3333                separator!("DIR"),
3334                separator!("DIR/SUBDIR"),
3335                separator!("DIR/SUBDIR/f.txt"),
3336                separator!("DIR/e.txt"),
3337                separator!("a.txt"),
3338                separator!("b.txt"),
3339                separator!("d.txt")
3340            ]
3341        );
3342    });
3343
3344    project_b
3345        .update(cx_b, |project, cx| {
3346            project.copy_entry(entry.id, None, Path::new("f.txt"), cx)
3347        })
3348        .await
3349        .unwrap()
3350        .unwrap();
3351
3352    worktree_a.read_with(cx_a, |worktree, _| {
3353        assert_eq!(
3354            worktree
3355                .paths()
3356                .map(|p| p.to_string_lossy())
3357                .collect::<Vec<_>>(),
3358            [
3359                separator!("DIR"),
3360                separator!("DIR/SUBDIR"),
3361                separator!("DIR/SUBDIR/f.txt"),
3362                separator!("DIR/e.txt"),
3363                separator!("a.txt"),
3364                separator!("b.txt"),
3365                separator!("d.txt"),
3366                separator!("f.txt")
3367            ]
3368        );
3369    });
3370
3371    worktree_b.read_with(cx_b, |worktree, _| {
3372        assert_eq!(
3373            worktree
3374                .paths()
3375                .map(|p| p.to_string_lossy())
3376                .collect::<Vec<_>>(),
3377            [
3378                separator!("DIR"),
3379                separator!("DIR/SUBDIR"),
3380                separator!("DIR/SUBDIR/f.txt"),
3381                separator!("DIR/e.txt"),
3382                separator!("a.txt"),
3383                separator!("b.txt"),
3384                separator!("d.txt"),
3385                separator!("f.txt")
3386            ]
3387        );
3388    });
3389
3390    project_b
3391        .update(cx_b, |project, cx| {
3392            project.delete_entry(dir_entry.id, false, cx).unwrap()
3393        })
3394        .await
3395        .unwrap();
3396    executor.run_until_parked();
3397
3398    worktree_a.read_with(cx_a, |worktree, _| {
3399        assert_eq!(
3400            worktree
3401                .paths()
3402                .map(|p| p.to_string_lossy())
3403                .collect::<Vec<_>>(),
3404            ["a.txt", "b.txt", "d.txt", "f.txt"]
3405        );
3406    });
3407
3408    worktree_b.read_with(cx_b, |worktree, _| {
3409        assert_eq!(
3410            worktree
3411                .paths()
3412                .map(|p| p.to_string_lossy())
3413                .collect::<Vec<_>>(),
3414            ["a.txt", "b.txt", "d.txt", "f.txt"]
3415        );
3416    });
3417
3418    project_b
3419        .update(cx_b, |project, cx| {
3420            project.delete_entry(entry.id, false, cx).unwrap()
3421        })
3422        .await
3423        .unwrap();
3424
3425    worktree_a.read_with(cx_a, |worktree, _| {
3426        assert_eq!(
3427            worktree
3428                .paths()
3429                .map(|p| p.to_string_lossy())
3430                .collect::<Vec<_>>(),
3431            ["a.txt", "b.txt", "f.txt"]
3432        );
3433    });
3434
3435    worktree_b.read_with(cx_b, |worktree, _| {
3436        assert_eq!(
3437            worktree
3438                .paths()
3439                .map(|p| p.to_string_lossy())
3440                .collect::<Vec<_>>(),
3441            ["a.txt", "b.txt", "f.txt"]
3442        );
3443    });
3444}
3445
3446#[gpui::test(iterations = 10)]
3447async fn test_local_settings(
3448    executor: BackgroundExecutor,
3449    cx_a: &mut TestAppContext,
3450    cx_b: &mut TestAppContext,
3451) {
3452    let mut server = TestServer::start(executor.clone()).await;
3453    let client_a = server.create_client(cx_a, "user_a").await;
3454    let client_b = server.create_client(cx_b, "user_b").await;
3455    server
3456        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3457        .await;
3458    let active_call_a = cx_a.read(ActiveCall::global);
3459
3460    // As client A, open a project that contains some local settings files
3461    client_a
3462        .fs()
3463        .insert_tree(
3464            "/dir",
3465            json!({
3466                ".zed": {
3467                    "settings.json": r#"{ "tab_size": 2 }"#
3468                },
3469                "a": {
3470                    ".zed": {
3471                        "settings.json": r#"{ "tab_size": 8 }"#
3472                    },
3473                    "a.txt": "a-contents",
3474                },
3475                "b": {
3476                    "b.txt": "b-contents",
3477                }
3478            }),
3479        )
3480        .await;
3481    let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
3482    executor.run_until_parked();
3483    let project_id = active_call_a
3484        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3485        .await
3486        .unwrap();
3487    executor.run_until_parked();
3488
3489    // As client B, join that project and observe the local settings.
3490    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3491
3492    let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
3493    executor.run_until_parked();
3494    cx_b.read(|cx| {
3495        let store = cx.global::<SettingsStore>();
3496        assert_eq!(
3497            store
3498                .local_settings(worktree_b.read(cx).id())
3499                .collect::<Vec<_>>(),
3500            &[
3501                (Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
3502                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3503            ]
3504        )
3505    });
3506
3507    // As client A, update a settings file. As Client B, see the changed settings.
3508    client_a
3509        .fs()
3510        .insert_file("/dir/.zed/settings.json", r#"{}"#.into())
3511        .await;
3512    executor.run_until_parked();
3513    cx_b.read(|cx| {
3514        let store = cx.global::<SettingsStore>();
3515        assert_eq!(
3516            store
3517                .local_settings(worktree_b.read(cx).id())
3518                .collect::<Vec<_>>(),
3519            &[
3520                (Path::new("").into(), r#"{}"#.to_string()),
3521                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3522            ]
3523        )
3524    });
3525
3526    // As client A, create and remove some settings files. As client B, see the changed settings.
3527    client_a
3528        .fs()
3529        .remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
3530        .await
3531        .unwrap();
3532    client_a
3533        .fs()
3534        .create_dir("/dir/b/.zed".as_ref())
3535        .await
3536        .unwrap();
3537    client_a
3538        .fs()
3539        .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
3540        .await;
3541    executor.run_until_parked();
3542    cx_b.read(|cx| {
3543        let store = cx.global::<SettingsStore>();
3544        assert_eq!(
3545            store
3546                .local_settings(worktree_b.read(cx).id())
3547                .collect::<Vec<_>>(),
3548            &[
3549                (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
3550                (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
3551            ]
3552        )
3553    });
3554
3555    // As client B, disconnect.
3556    server.forbid_connections();
3557    server.disconnect_client(client_b.peer_id().unwrap());
3558
3559    // As client A, change and remove settings files while client B is disconnected.
3560    client_a
3561        .fs()
3562        .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
3563        .await;
3564    client_a
3565        .fs()
3566        .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
3567        .await
3568        .unwrap();
3569    executor.run_until_parked();
3570
3571    // As client B, reconnect and see the changed settings.
3572    server.allow_connections();
3573    executor.advance_clock(RECEIVE_TIMEOUT);
3574    cx_b.read(|cx| {
3575        let store = cx.global::<SettingsStore>();
3576        assert_eq!(
3577            store
3578                .local_settings(worktree_b.read(cx).id())
3579                .collect::<Vec<_>>(),
3580            &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
3581        )
3582    });
3583}
3584
3585#[gpui::test(iterations = 10)]
3586async fn test_buffer_conflict_after_save(
3587    executor: BackgroundExecutor,
3588    cx_a: &mut TestAppContext,
3589    cx_b: &mut TestAppContext,
3590) {
3591    let mut server = TestServer::start(executor.clone()).await;
3592    let client_a = server.create_client(cx_a, "user_a").await;
3593    let client_b = server.create_client(cx_b, "user_b").await;
3594    server
3595        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3596        .await;
3597    let active_call_a = cx_a.read(ActiveCall::global);
3598
3599    client_a
3600        .fs()
3601        .insert_tree(
3602            path!("/dir"),
3603            json!({
3604                "a.txt": "a-contents",
3605            }),
3606        )
3607        .await;
3608    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3609    let project_id = active_call_a
3610        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3611        .await
3612        .unwrap();
3613    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3614
3615    // Open a buffer as client B
3616    let buffer_b = project_b
3617        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3618        .await
3619        .unwrap();
3620
3621    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "world ")], None, cx));
3622
3623    buffer_b.read_with(cx_b, |buf, _| {
3624        assert!(buf.is_dirty());
3625        assert!(!buf.has_conflict());
3626    });
3627
3628    project_b
3629        .update(cx_b, |project, cx| {
3630            project.save_buffer(buffer_b.clone(), cx)
3631        })
3632        .await
3633        .unwrap();
3634
3635    buffer_b.read_with(cx_b, |buffer_b, _| assert!(!buffer_b.is_dirty()));
3636
3637    buffer_b.read_with(cx_b, |buf, _| {
3638        assert!(!buf.has_conflict());
3639    });
3640
3641    buffer_b.update(cx_b, |buf, cx| buf.edit([(0..0, "hello ")], None, cx));
3642
3643    buffer_b.read_with(cx_b, |buf, _| {
3644        assert!(buf.is_dirty());
3645        assert!(!buf.has_conflict());
3646    });
3647}
3648
3649#[gpui::test(iterations = 10)]
3650async fn test_buffer_reloading(
3651    executor: BackgroundExecutor,
3652    cx_a: &mut TestAppContext,
3653    cx_b: &mut TestAppContext,
3654) {
3655    let mut server = TestServer::start(executor.clone()).await;
3656    let client_a = server.create_client(cx_a, "user_a").await;
3657    let client_b = server.create_client(cx_b, "user_b").await;
3658    server
3659        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3660        .await;
3661    let active_call_a = cx_a.read(ActiveCall::global);
3662
3663    client_a
3664        .fs()
3665        .insert_tree(
3666            path!("/dir"),
3667            json!({
3668                "a.txt": "a\nb\nc",
3669            }),
3670        )
3671        .await;
3672    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3673    let project_id = active_call_a
3674        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3675        .await
3676        .unwrap();
3677    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3678
3679    // Open a buffer as client B
3680    let buffer_b = project_b
3681        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3682        .await
3683        .unwrap();
3684
3685    buffer_b.read_with(cx_b, |buf, _| {
3686        assert!(!buf.is_dirty());
3687        assert!(!buf.has_conflict());
3688        assert_eq!(buf.line_ending(), LineEnding::Unix);
3689    });
3690
3691    let new_contents = Rope::from("d\ne\nf");
3692    client_a
3693        .fs()
3694        .save(
3695            path!("/dir/a.txt").as_ref(),
3696            &new_contents,
3697            LineEnding::Windows,
3698        )
3699        .await
3700        .unwrap();
3701
3702    executor.run_until_parked();
3703
3704    buffer_b.read_with(cx_b, |buf, _| {
3705        assert_eq!(buf.text(), new_contents.to_string());
3706        assert!(!buf.is_dirty());
3707        assert!(!buf.has_conflict());
3708        assert_eq!(buf.line_ending(), LineEnding::Windows);
3709    });
3710}
3711
3712#[gpui::test(iterations = 10)]
3713async fn test_editing_while_guest_opens_buffer(
3714    executor: BackgroundExecutor,
3715    cx_a: &mut TestAppContext,
3716    cx_b: &mut TestAppContext,
3717) {
3718    let mut server = TestServer::start(executor.clone()).await;
3719    let client_a = server.create_client(cx_a, "user_a").await;
3720    let client_b = server.create_client(cx_b, "user_b").await;
3721    server
3722        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3723        .await;
3724    let active_call_a = cx_a.read(ActiveCall::global);
3725
3726    client_a
3727        .fs()
3728        .insert_tree(path!("/dir"), json!({ "a.txt": "a-contents" }))
3729        .await;
3730    let (project_a, worktree_id) = client_a.build_local_project(path!("/dir"), cx_a).await;
3731    let project_id = active_call_a
3732        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3733        .await
3734        .unwrap();
3735    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3736
3737    // Open a buffer as client A
3738    let buffer_a = project_a
3739        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3740        .await
3741        .unwrap();
3742
3743    // Start opening the same buffer as client B
3744    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3745    let buffer_b = cx_b.executor().spawn(open_buffer);
3746
3747    // Edit the buffer as client A while client B is still opening it.
3748    cx_b.executor().simulate_random_delay().await;
3749    buffer_a.update(cx_a, |buf, cx| buf.edit([(0..0, "X")], None, cx));
3750    cx_b.executor().simulate_random_delay().await;
3751    buffer_a.update(cx_a, |buf, cx| buf.edit([(1..1, "Y")], None, cx));
3752
3753    let text = buffer_a.read_with(cx_a, |buf, _| buf.text());
3754    let buffer_b = buffer_b.await.unwrap();
3755    executor.run_until_parked();
3756
3757    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), text));
3758}
3759
3760#[gpui::test(iterations = 10)]
3761async fn test_leaving_worktree_while_opening_buffer(
3762    executor: BackgroundExecutor,
3763    cx_a: &mut TestAppContext,
3764    cx_b: &mut TestAppContext,
3765) {
3766    let mut server = TestServer::start(executor.clone()).await;
3767    let client_a = server.create_client(cx_a, "user_a").await;
3768    let client_b = server.create_client(cx_b, "user_b").await;
3769    server
3770        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3771        .await;
3772    let active_call_a = cx_a.read(ActiveCall::global);
3773
3774    client_a
3775        .fs()
3776        .insert_tree("/dir", json!({ "a.txt": "a-contents" }))
3777        .await;
3778    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3779    let project_id = active_call_a
3780        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3781        .await
3782        .unwrap();
3783    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3784
3785    // See that a guest has joined as client A.
3786    executor.run_until_parked();
3787
3788    project_a.read_with(cx_a, |p, _| assert_eq!(p.collaborators().len(), 1));
3789
3790    // Begin opening a buffer as client B, but leave the project before the open completes.
3791    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx));
3792    let buffer_b = cx_b.executor().spawn(open_buffer);
3793    cx_b.update(|_| drop(project_b));
3794    drop(buffer_b);
3795
3796    // See that the guest has left.
3797    executor.run_until_parked();
3798
3799    project_a.read_with(cx_a, |p, _| assert!(p.collaborators().is_empty()));
3800}
3801
3802#[gpui::test(iterations = 10)]
3803async fn test_canceling_buffer_opening(
3804    executor: BackgroundExecutor,
3805    cx_a: &mut TestAppContext,
3806    cx_b: &mut TestAppContext,
3807) {
3808    let mut server = TestServer::start(executor.clone()).await;
3809    let client_a = server.create_client(cx_a, "user_a").await;
3810    let client_b = server.create_client(cx_b, "user_b").await;
3811    server
3812        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
3813        .await;
3814    let active_call_a = cx_a.read(ActiveCall::global);
3815
3816    client_a
3817        .fs()
3818        .insert_tree(
3819            "/dir",
3820            json!({
3821                "a.txt": "abc",
3822            }),
3823        )
3824        .await;
3825    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
3826    let project_id = active_call_a
3827        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3828        .await
3829        .unwrap();
3830    let project_b = client_b.join_remote_project(project_id, cx_b).await;
3831
3832    let buffer_a = project_a
3833        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
3834        .await
3835        .unwrap();
3836
3837    // Open a buffer as client B but cancel after a random amount of time.
3838    let buffer_b = project_b.update(cx_b, |p, cx| {
3839        p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3840    });
3841    executor.simulate_random_delay().await;
3842    drop(buffer_b);
3843
3844    // Try opening the same buffer again as client B, and ensure we can
3845    // still do it despite the cancellation above.
3846    let buffer_b = project_b
3847        .update(cx_b, |p, cx| {
3848            p.open_buffer_by_id(buffer_a.read_with(cx_a, |a, _| a.remote_id()), cx)
3849        })
3850        .await
3851        .unwrap();
3852
3853    buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "abc"));
3854}
3855
3856#[gpui::test(iterations = 10)]
3857async fn test_leaving_project(
3858    executor: BackgroundExecutor,
3859    cx_a: &mut TestAppContext,
3860    cx_b: &mut TestAppContext,
3861    cx_c: &mut TestAppContext,
3862) {
3863    let mut server = TestServer::start(executor.clone()).await;
3864    let client_a = server.create_client(cx_a, "user_a").await;
3865    let client_b = server.create_client(cx_b, "user_b").await;
3866    let client_c = server.create_client(cx_c, "user_c").await;
3867    server
3868        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
3869        .await;
3870    let active_call_a = cx_a.read(ActiveCall::global);
3871
3872    client_a
3873        .fs()
3874        .insert_tree(
3875            "/a",
3876            json!({
3877                "a.txt": "a-contents",
3878                "b.txt": "b-contents",
3879            }),
3880        )
3881        .await;
3882    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
3883    let project_id = active_call_a
3884        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
3885        .await
3886        .unwrap();
3887    let project_b1 = client_b.join_remote_project(project_id, cx_b).await;
3888    let project_c = client_c.join_remote_project(project_id, cx_c).await;
3889
3890    // Client A sees that a guest has joined.
3891    executor.run_until_parked();
3892
3893    project_a.read_with(cx_a, |project, _| {
3894        assert_eq!(project.collaborators().len(), 2);
3895    });
3896
3897    project_b1.read_with(cx_b, |project, _| {
3898        assert_eq!(project.collaborators().len(), 2);
3899    });
3900
3901    project_c.read_with(cx_c, |project, _| {
3902        assert_eq!(project.collaborators().len(), 2);
3903    });
3904
3905    // Client B opens a buffer.
3906    let buffer_b1 = project_b1
3907        .update(cx_b, |project, cx| {
3908            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3909            project.open_buffer((worktree_id, "a.txt"), cx)
3910        })
3911        .await
3912        .unwrap();
3913
3914    buffer_b1.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3915
3916    // Drop client B's project and ensure client A and client C observe client B leaving.
3917    cx_b.update(|_| drop(project_b1));
3918    executor.run_until_parked();
3919
3920    project_a.read_with(cx_a, |project, _| {
3921        assert_eq!(project.collaborators().len(), 1);
3922    });
3923
3924    project_c.read_with(cx_c, |project, _| {
3925        assert_eq!(project.collaborators().len(), 1);
3926    });
3927
3928    // Client B re-joins the project and can open buffers as before.
3929    let project_b2 = client_b.join_remote_project(project_id, cx_b).await;
3930    executor.run_until_parked();
3931
3932    project_a.read_with(cx_a, |project, _| {
3933        assert_eq!(project.collaborators().len(), 2);
3934    });
3935
3936    project_b2.read_with(cx_b, |project, _| {
3937        assert_eq!(project.collaborators().len(), 2);
3938    });
3939
3940    project_c.read_with(cx_c, |project, _| {
3941        assert_eq!(project.collaborators().len(), 2);
3942    });
3943
3944    let buffer_b2 = project_b2
3945        .update(cx_b, |project, cx| {
3946            let worktree_id = project.worktrees(cx).next().unwrap().read(cx).id();
3947            project.open_buffer((worktree_id, "a.txt"), cx)
3948        })
3949        .await
3950        .unwrap();
3951
3952    buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
3953
3954    project_a.read_with(cx_a, |project, _| {
3955        assert_eq!(project.collaborators().len(), 2);
3956    });
3957
3958    // Drop client B's connection and ensure client A and client C observe client B leaving.
3959    client_b.disconnect(&cx_b.to_async());
3960    executor.advance_clock(RECONNECT_TIMEOUT);
3961
3962    project_a.read_with(cx_a, |project, _| {
3963        assert_eq!(project.collaborators().len(), 1);
3964    });
3965
3966    project_b2.read_with(cx_b, |project, cx| {
3967        assert!(project.is_disconnected(cx));
3968    });
3969
3970    project_c.read_with(cx_c, |project, _| {
3971        assert_eq!(project.collaborators().len(), 1);
3972    });
3973
3974    // Client B can't join the project, unless they re-join the room.
3975    cx_b.spawn(|cx| {
3976        Project::in_room(
3977            project_id,
3978            client_b.app_state.client.clone(),
3979            client_b.user_store().clone(),
3980            client_b.language_registry().clone(),
3981            FakeFs::new(cx.background_executor().clone()),
3982            cx,
3983        )
3984    })
3985    .await
3986    .unwrap_err();
3987
3988    // Simulate connection loss for client C and ensure client A observes client C leaving the project.
3989    client_c.wait_for_current_user(cx_c).await;
3990    server.forbid_connections();
3991    server.disconnect_client(client_c.peer_id().unwrap());
3992    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
3993    executor.run_until_parked();
3994
3995    project_a.read_with(cx_a, |project, _| {
3996        assert_eq!(project.collaborators().len(), 0);
3997    });
3998
3999    project_b2.read_with(cx_b, |project, cx| {
4000        assert!(project.is_disconnected(cx));
4001    });
4002
4003    project_c.read_with(cx_c, |project, cx| {
4004        assert!(project.is_disconnected(cx));
4005    });
4006}
4007
4008#[gpui::test(iterations = 10)]
4009async fn test_collaborating_with_diagnostics(
4010    executor: BackgroundExecutor,
4011    cx_a: &mut TestAppContext,
4012    cx_b: &mut TestAppContext,
4013    cx_c: &mut TestAppContext,
4014) {
4015    let mut server = TestServer::start(executor.clone()).await;
4016    let client_a = server.create_client(cx_a, "user_a").await;
4017    let client_b = server.create_client(cx_b, "user_b").await;
4018    let client_c = server.create_client(cx_c, "user_c").await;
4019    server
4020        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
4021        .await;
4022    let active_call_a = cx_a.read(ActiveCall::global);
4023
4024    client_a.language_registry().add(Arc::new(Language::new(
4025        LanguageConfig {
4026            name: "Rust".into(),
4027            matcher: LanguageMatcher {
4028                path_suffixes: vec!["rs".to_string()],
4029                ..Default::default()
4030            },
4031            ..Default::default()
4032        },
4033        Some(tree_sitter_rust::LANGUAGE.into()),
4034    )));
4035    let mut fake_language_servers = client_a
4036        .language_registry()
4037        .register_fake_lsp("Rust", Default::default());
4038
4039    // Share a project as client A
4040    client_a
4041        .fs()
4042        .insert_tree(
4043            path!("/a"),
4044            json!({
4045                "a.rs": "let one = two",
4046                "other.rs": "",
4047            }),
4048        )
4049        .await;
4050    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4051
4052    // Cause the language server to start.
4053    let _buffer = project_a
4054        .update(cx_a, |project, cx| {
4055            project.open_local_buffer_with_lsp(path!("/a/other.rs"), cx)
4056        })
4057        .await
4058        .unwrap();
4059
4060    // Simulate a language server reporting errors for a file.
4061    let mut fake_language_server = fake_language_servers.next().await.unwrap();
4062    fake_language_server
4063        .receive_notification::<lsp::notification::DidOpenTextDocument>()
4064        .await;
4065    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4066        &lsp::PublishDiagnosticsParams {
4067            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4068            version: None,
4069            diagnostics: vec![lsp::Diagnostic {
4070                severity: Some(lsp::DiagnosticSeverity::WARNING),
4071                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4072                message: "message 0".to_string(),
4073                ..Default::default()
4074            }],
4075        },
4076    );
4077
4078    // Client A shares the project and, simultaneously, the language server
4079    // publishes a diagnostic. This is done to ensure that the server always
4080    // observes the latest diagnostics for a worktree.
4081    let project_id = active_call_a
4082        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4083        .await
4084        .unwrap();
4085    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4086        &lsp::PublishDiagnosticsParams {
4087            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4088            version: None,
4089            diagnostics: vec![lsp::Diagnostic {
4090                severity: Some(lsp::DiagnosticSeverity::ERROR),
4091                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4092                message: "message 1".to_string(),
4093                ..Default::default()
4094            }],
4095        },
4096    );
4097
4098    // Join the worktree as client B.
4099    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4100
4101    // Wait for server to see the diagnostics update.
4102    executor.run_until_parked();
4103
4104    // Ensure client B observes the new diagnostics.
4105
4106    project_b.read_with(cx_b, |project, cx| {
4107        assert_eq!(
4108            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4109            &[(
4110                ProjectPath {
4111                    worktree_id,
4112                    path: Arc::from(Path::new("a.rs")),
4113                },
4114                LanguageServerId(0),
4115                DiagnosticSummary {
4116                    error_count: 1,
4117                    warning_count: 0,
4118                },
4119            )]
4120        )
4121    });
4122
4123    // Join project as client C and observe the diagnostics.
4124    let project_c = client_c.join_remote_project(project_id, cx_c).await;
4125    executor.run_until_parked();
4126    let project_c_diagnostic_summaries =
4127        Rc::new(RefCell::new(project_c.read_with(cx_c, |project, cx| {
4128            project.diagnostic_summaries(false, cx).collect::<Vec<_>>()
4129        })));
4130    project_c.update(cx_c, |_, cx| {
4131        let summaries = project_c_diagnostic_summaries.clone();
4132        cx.subscribe(&project_c, {
4133            move |p, _, event, cx| {
4134                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4135                    *summaries.borrow_mut() = p.diagnostic_summaries(false, cx).collect();
4136                }
4137            }
4138        })
4139        .detach();
4140    });
4141
4142    executor.run_until_parked();
4143    assert_eq!(
4144        project_c_diagnostic_summaries.borrow().as_slice(),
4145        &[(
4146            ProjectPath {
4147                worktree_id,
4148                path: Arc::from(Path::new("a.rs")),
4149            },
4150            LanguageServerId(0),
4151            DiagnosticSummary {
4152                error_count: 1,
4153                warning_count: 0,
4154            },
4155        )]
4156    );
4157
4158    // Simulate a language server reporting more errors for a file.
4159    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4160        &lsp::PublishDiagnosticsParams {
4161            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4162            version: None,
4163            diagnostics: vec![
4164                lsp::Diagnostic {
4165                    severity: Some(lsp::DiagnosticSeverity::ERROR),
4166                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 7)),
4167                    message: "message 1".to_string(),
4168                    ..Default::default()
4169                },
4170                lsp::Diagnostic {
4171                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4172                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 13)),
4173                    message: "message 2".to_string(),
4174                    ..Default::default()
4175                },
4176            ],
4177        },
4178    );
4179
4180    // Clients B and C get the updated summaries
4181    executor.run_until_parked();
4182
4183    project_b.read_with(cx_b, |project, cx| {
4184        assert_eq!(
4185            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4186            [(
4187                ProjectPath {
4188                    worktree_id,
4189                    path: Arc::from(Path::new("a.rs")),
4190                },
4191                LanguageServerId(0),
4192                DiagnosticSummary {
4193                    error_count: 1,
4194                    warning_count: 1,
4195                },
4196            )]
4197        );
4198    });
4199
4200    project_c.read_with(cx_c, |project, cx| {
4201        assert_eq!(
4202            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4203            [(
4204                ProjectPath {
4205                    worktree_id,
4206                    path: Arc::from(Path::new("a.rs")),
4207                },
4208                LanguageServerId(0),
4209                DiagnosticSummary {
4210                    error_count: 1,
4211                    warning_count: 1,
4212                },
4213            )]
4214        );
4215    });
4216
4217    // Open the file with the errors on client B. They should be present.
4218    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4219    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4220
4221    buffer_b.read_with(cx_b, |buffer, _| {
4222        assert_eq!(
4223            buffer
4224                .snapshot()
4225                .diagnostics_in_range::<_, Point>(0..buffer.len(), false)
4226                .collect::<Vec<_>>(),
4227            &[
4228                DiagnosticEntry {
4229                    range: Point::new(0, 4)..Point::new(0, 7),
4230                    diagnostic: Diagnostic {
4231                        group_id: 2,
4232                        message: "message 1".to_string(),
4233                        severity: lsp::DiagnosticSeverity::ERROR,
4234                        is_primary: true,
4235                        ..Default::default()
4236                    }
4237                },
4238                DiagnosticEntry {
4239                    range: Point::new(0, 10)..Point::new(0, 13),
4240                    diagnostic: Diagnostic {
4241                        group_id: 3,
4242                        severity: lsp::DiagnosticSeverity::WARNING,
4243                        message: "message 2".to_string(),
4244                        is_primary: true,
4245                        ..Default::default()
4246                    }
4247                }
4248            ]
4249        );
4250    });
4251
4252    // Simulate a language server reporting no errors for a file.
4253    fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4254        &lsp::PublishDiagnosticsParams {
4255            uri: lsp::Url::from_file_path(path!("/a/a.rs")).unwrap(),
4256            version: None,
4257            diagnostics: vec![],
4258        },
4259    );
4260    executor.run_until_parked();
4261
4262    project_a.read_with(cx_a, |project, cx| {
4263        assert_eq!(
4264            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4265            []
4266        )
4267    });
4268
4269    project_b.read_with(cx_b, |project, cx| {
4270        assert_eq!(
4271            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4272            []
4273        )
4274    });
4275
4276    project_c.read_with(cx_c, |project, cx| {
4277        assert_eq!(
4278            project.diagnostic_summaries(false, cx).collect::<Vec<_>>(),
4279            []
4280        )
4281    });
4282}
4283
4284#[gpui::test(iterations = 10)]
4285async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering(
4286    executor: BackgroundExecutor,
4287    cx_a: &mut TestAppContext,
4288    cx_b: &mut TestAppContext,
4289) {
4290    let mut server = TestServer::start(executor.clone()).await;
4291    let client_a = server.create_client(cx_a, "user_a").await;
4292    let client_b = server.create_client(cx_b, "user_b").await;
4293    server
4294        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4295        .await;
4296
4297    client_a.language_registry().add(rust_lang());
4298    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4299        "Rust",
4300        FakeLspAdapter {
4301            disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()),
4302            disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()],
4303            ..Default::default()
4304        },
4305    );
4306
4307    let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"];
4308    client_a
4309        .fs()
4310        .insert_tree(
4311            path!("/test"),
4312            json!({
4313                "one.rs": "const ONE: usize = 1;",
4314                "two.rs": "const TWO: usize = 2;",
4315                "three.rs": "const THREE: usize = 3;",
4316                "four.rs": "const FOUR: usize = 3;",
4317                "five.rs": "const FIVE: usize = 3;",
4318            }),
4319        )
4320        .await;
4321
4322    let (project_a, worktree_id) = client_a.build_local_project(path!("/test"), cx_a).await;
4323
4324    // Share a project as client A
4325    let active_call_a = cx_a.read(ActiveCall::global);
4326    let project_id = active_call_a
4327        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4328        .await
4329        .unwrap();
4330
4331    // Join the project as client B and open all three files.
4332    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4333    let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| {
4334        project_b.update(cx_b, |p, cx| {
4335            p.open_buffer_with_lsp((worktree_id, file_name), cx)
4336        })
4337    }))
4338    .await
4339    .unwrap();
4340
4341    // Simulate a language server reporting errors for a file.
4342    let fake_language_server = fake_language_servers.next().await.unwrap();
4343    fake_language_server
4344        .request::<lsp::request::WorkDoneProgressCreate>(lsp::WorkDoneProgressCreateParams {
4345            token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4346        })
4347        .await
4348        .into_response()
4349        .unwrap();
4350    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4351        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4352        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin(
4353            lsp::WorkDoneProgressBegin {
4354                title: "Progress Began".into(),
4355                ..Default::default()
4356            },
4357        )),
4358    });
4359    for file_name in file_names {
4360        fake_language_server.notify::<lsp::notification::PublishDiagnostics>(
4361            &lsp::PublishDiagnosticsParams {
4362                uri: lsp::Url::from_file_path(Path::new(path!("/test")).join(file_name)).unwrap(),
4363                version: None,
4364                diagnostics: vec![lsp::Diagnostic {
4365                    severity: Some(lsp::DiagnosticSeverity::WARNING),
4366                    source: Some("the-disk-based-diagnostics-source".into()),
4367                    range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
4368                    message: "message one".to_string(),
4369                    ..Default::default()
4370                }],
4371            },
4372        );
4373    }
4374    fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
4375        token: lsp::NumberOrString::String("the-disk-based-token".to_string()),
4376        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End(
4377            lsp::WorkDoneProgressEnd { message: None },
4378        )),
4379    });
4380
4381    // When the "disk base diagnostics finished" message is received, the buffers'
4382    // diagnostics are expected to be present.
4383    let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false));
4384    project_b.update(cx_b, {
4385        let project_b = project_b.clone();
4386        let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone();
4387        move |_, cx| {
4388            cx.subscribe(&project_b, move |_, _, event, cx| {
4389                if let project::Event::DiskBasedDiagnosticsFinished { .. } = event {
4390                    disk_based_diagnostics_finished.store(true, SeqCst);
4391                    for (buffer, _) in &guest_buffers {
4392                        assert_eq!(
4393                            buffer
4394                                .read(cx)
4395                                .snapshot()
4396                                .diagnostics_in_range::<_, usize>(0..5, false)
4397                                .count(),
4398                            1,
4399                            "expected a diagnostic for buffer {:?}",
4400                            buffer.read(cx).file().unwrap().path(),
4401                        );
4402                    }
4403                }
4404            })
4405            .detach();
4406        }
4407    });
4408
4409    executor.run_until_parked();
4410    assert!(disk_based_diagnostics_finished.load(SeqCst));
4411}
4412
4413#[gpui::test(iterations = 10)]
4414async fn test_reloading_buffer_manually(
4415    executor: BackgroundExecutor,
4416    cx_a: &mut TestAppContext,
4417    cx_b: &mut TestAppContext,
4418) {
4419    let mut server = TestServer::start(executor.clone()).await;
4420    let client_a = server.create_client(cx_a, "user_a").await;
4421    let client_b = server.create_client(cx_b, "user_b").await;
4422    server
4423        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4424        .await;
4425    let active_call_a = cx_a.read(ActiveCall::global);
4426
4427    client_a
4428        .fs()
4429        .insert_tree(path!("/a"), json!({ "a.rs": "let one = 1;" }))
4430        .await;
4431    let (project_a, worktree_id) = client_a.build_local_project(path!("/a"), cx_a).await;
4432    let buffer_a = project_a
4433        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4434        .await
4435        .unwrap();
4436    let project_id = active_call_a
4437        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4438        .await
4439        .unwrap();
4440
4441    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4442
4443    let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx));
4444    let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap();
4445    buffer_b.update(cx_b, |buffer, cx| {
4446        buffer.edit([(4..7, "six")], None, cx);
4447        buffer.edit([(10..11, "6")], None, cx);
4448        assert_eq!(buffer.text(), "let six = 6;");
4449        assert!(buffer.is_dirty());
4450        assert!(!buffer.has_conflict());
4451    });
4452    executor.run_until_parked();
4453
4454    buffer_a.read_with(cx_a, |buffer, _| assert_eq!(buffer.text(), "let six = 6;"));
4455
4456    client_a
4457        .fs()
4458        .save(
4459            path!("/a/a.rs").as_ref(),
4460            &Rope::from("let seven = 7;"),
4461            LineEnding::Unix,
4462        )
4463        .await
4464        .unwrap();
4465    executor.run_until_parked();
4466
4467    buffer_a.read_with(cx_a, |buffer, _| assert!(buffer.has_conflict()));
4468
4469    buffer_b.read_with(cx_b, |buffer, _| assert!(buffer.has_conflict()));
4470
4471    project_b
4472        .update(cx_b, |project, cx| {
4473            project.reload_buffers(HashSet::from_iter([buffer_b.clone()]), true, cx)
4474        })
4475        .await
4476        .unwrap();
4477
4478    buffer_a.read_with(cx_a, |buffer, _| {
4479        assert_eq!(buffer.text(), "let seven = 7;");
4480        assert!(!buffer.is_dirty());
4481        assert!(!buffer.has_conflict());
4482    });
4483
4484    buffer_b.read_with(cx_b, |buffer, _| {
4485        assert_eq!(buffer.text(), "let seven = 7;");
4486        assert!(!buffer.is_dirty());
4487        assert!(!buffer.has_conflict());
4488    });
4489
4490    buffer_a.update(cx_a, |buffer, cx| {
4491        // Undoing on the host is a no-op when the reload was initiated by the guest.
4492        buffer.undo(cx);
4493        assert_eq!(buffer.text(), "let seven = 7;");
4494        assert!(!buffer.is_dirty());
4495        assert!(!buffer.has_conflict());
4496    });
4497    buffer_b.update(cx_b, |buffer, cx| {
4498        // Undoing on the guest rolls back the buffer to before it was reloaded but the conflict gets cleared.
4499        buffer.undo(cx);
4500        assert_eq!(buffer.text(), "let six = 6;");
4501        assert!(buffer.is_dirty());
4502        assert!(!buffer.has_conflict());
4503    });
4504}
4505
4506#[gpui::test(iterations = 10)]
4507async fn test_formatting_buffer(
4508    executor: BackgroundExecutor,
4509    cx_a: &mut TestAppContext,
4510    cx_b: &mut TestAppContext,
4511) {
4512    let mut server = TestServer::start(executor.clone()).await;
4513    let client_a = server.create_client(cx_a, "user_a").await;
4514    let client_b = server.create_client(cx_b, "user_b").await;
4515    server
4516        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4517        .await;
4518    let active_call_a = cx_a.read(ActiveCall::global);
4519
4520    client_a.language_registry().add(rust_lang());
4521    let mut fake_language_servers = client_a
4522        .language_registry()
4523        .register_fake_lsp("Rust", FakeLspAdapter::default());
4524
4525    // Here we insert a fake tree with a directory that exists on disk. This is needed
4526    // because later we'll invoke a command, which requires passing a working directory
4527    // that points to a valid location on disk.
4528    let directory = env::current_dir().unwrap();
4529    client_a
4530        .fs()
4531        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
4532        .await;
4533    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4534    let project_id = active_call_a
4535        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4536        .await
4537        .unwrap();
4538    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4539
4540    let buffer_b = project_b
4541        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx))
4542        .await
4543        .unwrap();
4544
4545    let _handle = project_b.update(cx_b, |project, cx| {
4546        project.register_buffer_with_language_servers(&buffer_b, cx)
4547    });
4548    let fake_language_server = fake_language_servers.next().await.unwrap();
4549    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4550        Ok(Some(vec![
4551            lsp::TextEdit {
4552                range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
4553                new_text: "h".to_string(),
4554            },
4555            lsp::TextEdit {
4556                range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
4557                new_text: "y".to_string(),
4558            },
4559        ]))
4560    });
4561
4562    project_b
4563        .update(cx_b, |project, cx| {
4564            project.format(
4565                HashSet::from_iter([buffer_b.clone()]),
4566                LspFormatTarget::Buffers,
4567                true,
4568                FormatTrigger::Save,
4569                cx,
4570            )
4571        })
4572        .await
4573        .unwrap();
4574
4575    // The edits from the LSP are applied, and a final newline is added.
4576    assert_eq!(
4577        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4578        "let honey = \"two\"\n"
4579    );
4580
4581    // There is no `awk` command on Windows.
4582    #[cfg(not(target_os = "windows"))]
4583    {
4584        // Ensure buffer can be formatted using an external command. Notice how the
4585        // host's configuration is honored as opposed to using the guest's settings.
4586        cx_a.update(|cx| {
4587            SettingsStore::update_global(cx, |store, cx| {
4588                store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4589                    file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4590                        vec![Formatter::External {
4591                            command: "awk".into(),
4592                            arguments: Some(
4593                                vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
4594                            ),
4595                        }]
4596                        .into(),
4597                    )));
4598                });
4599            });
4600        });
4601
4602        executor.allow_parking();
4603        project_b
4604            .update(cx_b, |project, cx| {
4605                project.format(
4606                    HashSet::from_iter([buffer_b.clone()]),
4607                    LspFormatTarget::Buffers,
4608                    true,
4609                    FormatTrigger::Save,
4610                    cx,
4611                )
4612            })
4613            .await
4614            .unwrap();
4615        assert_eq!(
4616            buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4617            format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
4618        );
4619    }
4620}
4621
4622#[gpui::test(iterations = 10)]
4623async fn test_prettier_formatting_buffer(
4624    executor: BackgroundExecutor,
4625    cx_a: &mut TestAppContext,
4626    cx_b: &mut TestAppContext,
4627) {
4628    let mut server = TestServer::start(executor.clone()).await;
4629    let client_a = server.create_client(cx_a, "user_a").await;
4630    let client_b = server.create_client(cx_b, "user_b").await;
4631    server
4632        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4633        .await;
4634    let active_call_a = cx_a.read(ActiveCall::global);
4635
4636    let test_plugin = "test_plugin";
4637
4638    client_a.language_registry().add(Arc::new(Language::new(
4639        LanguageConfig {
4640            name: "TypeScript".into(),
4641            matcher: LanguageMatcher {
4642                path_suffixes: vec!["ts".to_string()],
4643                ..Default::default()
4644            },
4645            ..Default::default()
4646        },
4647        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
4648    )));
4649    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4650        "TypeScript",
4651        FakeLspAdapter {
4652            prettier_plugins: vec![test_plugin],
4653            ..Default::default()
4654        },
4655    );
4656
4657    // Here we insert a fake tree with a directory that exists on disk. This is needed
4658    // because later we'll invoke a command, which requires passing a working directory
4659    // that points to a valid location on disk.
4660    let directory = env::current_dir().unwrap();
4661    let buffer_text = "let one = \"two\"";
4662    client_a
4663        .fs()
4664        .insert_tree(&directory, json!({ "a.ts": buffer_text }))
4665        .await;
4666    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
4667    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
4668    let open_buffer = project_a.update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.ts"), cx));
4669    let buffer_a = cx_a.executor().spawn(open_buffer).await.unwrap();
4670
4671    let project_id = active_call_a
4672        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4673        .await
4674        .unwrap();
4675    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4676    let (buffer_b, _) = project_b
4677        .update(cx_b, |p, cx| {
4678            p.open_buffer_with_lsp((worktree_id, "a.ts"), cx)
4679        })
4680        .await
4681        .unwrap();
4682
4683    cx_a.update(|cx| {
4684        SettingsStore::update_global(cx, |store, cx| {
4685            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4686                file.defaults.formatter = Some(SelectedFormatter::Auto);
4687                file.defaults.prettier = Some(PrettierSettings {
4688                    allowed: true,
4689                    ..PrettierSettings::default()
4690                });
4691            });
4692        });
4693    });
4694    cx_b.update(|cx| {
4695        SettingsStore::update_global(cx, |store, cx| {
4696            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
4697                file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
4698                    vec![Formatter::LanguageServer { name: None }].into(),
4699                )));
4700                file.defaults.prettier = Some(PrettierSettings {
4701                    allowed: true,
4702                    ..PrettierSettings::default()
4703                });
4704            });
4705        });
4706    });
4707    let fake_language_server = fake_language_servers.next().await.unwrap();
4708    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(|_, _| async move {
4709        panic!(
4710            "Unexpected: prettier should be preferred since it's enabled and language supports it"
4711        )
4712    });
4713
4714    project_b
4715        .update(cx_b, |project, cx| {
4716            project.format(
4717                HashSet::from_iter([buffer_b.clone()]),
4718                LspFormatTarget::Buffers,
4719                true,
4720                FormatTrigger::Save,
4721                cx,
4722            )
4723        })
4724        .await
4725        .unwrap();
4726
4727    executor.run_until_parked();
4728    assert_eq!(
4729        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4730        buffer_text.to_string() + "\n" + prettier_format_suffix,
4731        "Prettier formatting was not applied to client buffer after client's request"
4732    );
4733
4734    project_a
4735        .update(cx_a, |project, cx| {
4736            project.format(
4737                HashSet::from_iter([buffer_a.clone()]),
4738                LspFormatTarget::Buffers,
4739                true,
4740                FormatTrigger::Manual,
4741                cx,
4742            )
4743        })
4744        .await
4745        .unwrap();
4746
4747    executor.run_until_parked();
4748    assert_eq!(
4749        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
4750        buffer_text.to_string() + "\n" + prettier_format_suffix + "\n" + prettier_format_suffix,
4751        "Prettier formatting was not applied to client buffer after host's request"
4752    );
4753}
4754
4755#[gpui::test(iterations = 10)]
4756async fn test_definition(
4757    executor: BackgroundExecutor,
4758    cx_a: &mut TestAppContext,
4759    cx_b: &mut TestAppContext,
4760) {
4761    let mut server = TestServer::start(executor.clone()).await;
4762    let client_a = server.create_client(cx_a, "user_a").await;
4763    let client_b = server.create_client(cx_b, "user_b").await;
4764    server
4765        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4766        .await;
4767    let active_call_a = cx_a.read(ActiveCall::global);
4768
4769    let mut fake_language_servers = client_a
4770        .language_registry()
4771        .register_fake_lsp("Rust", Default::default());
4772    client_a.language_registry().add(rust_lang());
4773
4774    client_a
4775        .fs()
4776        .insert_tree(
4777            path!("/root"),
4778            json!({
4779                "dir-1": {
4780                    "a.rs": "const ONE: usize = b::TWO + b::THREE;",
4781                },
4782                "dir-2": {
4783                    "b.rs": "const TWO: c::T2 = 2;\nconst THREE: usize = 3;",
4784                    "c.rs": "type T2 = usize;",
4785                }
4786            }),
4787        )
4788        .await;
4789    let (project_a, worktree_id) = client_a
4790        .build_local_project(path!("/root/dir-1"), cx_a)
4791        .await;
4792    let project_id = active_call_a
4793        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4794        .await
4795        .unwrap();
4796    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4797
4798    // Open the file on client B.
4799    let (buffer_b, _handle) = project_b
4800        .update(cx_b, |p, cx| {
4801            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
4802        })
4803        .await
4804        .unwrap();
4805
4806    // Request the definition of a symbol as the guest.
4807    let fake_language_server = fake_language_servers.next().await.unwrap();
4808    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4809        |_, _| async move {
4810            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4811                lsp::Location::new(
4812                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4813                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
4814                ),
4815            )))
4816        },
4817    );
4818
4819    let definitions_1 = project_b
4820        .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
4821        .await
4822        .unwrap();
4823    cx_b.read(|cx| {
4824        assert_eq!(definitions_1.len(), 1);
4825        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4826        let target_buffer = definitions_1[0].target.buffer.read(cx);
4827        assert_eq!(
4828            target_buffer.text(),
4829            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4830        );
4831        assert_eq!(
4832            definitions_1[0].target.range.to_point(target_buffer),
4833            Point::new(0, 6)..Point::new(0, 9)
4834        );
4835    });
4836
4837    // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
4838    // the previous call to `definition`.
4839    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
4840        |_, _| async move {
4841            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4842                lsp::Location::new(
4843                    lsp::Url::from_file_path(path!("/root/dir-2/b.rs")).unwrap(),
4844                    lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)),
4845                ),
4846            )))
4847        },
4848    );
4849
4850    let definitions_2 = project_b
4851        .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
4852        .await
4853        .unwrap();
4854    cx_b.read(|cx| {
4855        assert_eq!(definitions_2.len(), 1);
4856        assert_eq!(project_b.read(cx).worktrees(cx).count(), 2);
4857        let target_buffer = definitions_2[0].target.buffer.read(cx);
4858        assert_eq!(
4859            target_buffer.text(),
4860            "const TWO: c::T2 = 2;\nconst THREE: usize = 3;"
4861        );
4862        assert_eq!(
4863            definitions_2[0].target.range.to_point(target_buffer),
4864            Point::new(1, 6)..Point::new(1, 11)
4865        );
4866    });
4867    assert_eq!(
4868        definitions_1[0].target.buffer,
4869        definitions_2[0].target.buffer
4870    );
4871
4872    fake_language_server.set_request_handler::<lsp::request::GotoTypeDefinition, _, _>(
4873        |req, _| async move {
4874            assert_eq!(
4875                req.text_document_position_params.position,
4876                lsp::Position::new(0, 7)
4877            );
4878            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
4879                lsp::Location::new(
4880                    lsp::Url::from_file_path(path!("/root/dir-2/c.rs")).unwrap(),
4881                    lsp::Range::new(lsp::Position::new(0, 5), lsp::Position::new(0, 7)),
4882                ),
4883            )))
4884        },
4885    );
4886
4887    let type_definitions = project_b
4888        .update(cx_b, |p, cx| p.type_definition(&buffer_b, 7, cx))
4889        .await
4890        .unwrap();
4891    cx_b.read(|cx| {
4892        assert_eq!(type_definitions.len(), 1);
4893        let target_buffer = type_definitions[0].target.buffer.read(cx);
4894        assert_eq!(target_buffer.text(), "type T2 = usize;");
4895        assert_eq!(
4896            type_definitions[0].target.range.to_point(target_buffer),
4897            Point::new(0, 5)..Point::new(0, 7)
4898        );
4899    });
4900}
4901
4902#[gpui::test(iterations = 10)]
4903async fn test_references(
4904    executor: BackgroundExecutor,
4905    cx_a: &mut TestAppContext,
4906    cx_b: &mut TestAppContext,
4907) {
4908    let mut server = TestServer::start(executor.clone()).await;
4909    let client_a = server.create_client(cx_a, "user_a").await;
4910    let client_b = server.create_client(cx_b, "user_b").await;
4911    server
4912        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
4913        .await;
4914    let active_call_a = cx_a.read(ActiveCall::global);
4915
4916    client_a.language_registry().add(rust_lang());
4917    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
4918        "Rust",
4919        FakeLspAdapter {
4920            name: "my-fake-lsp-adapter",
4921            capabilities: lsp::ServerCapabilities {
4922                references_provider: Some(lsp::OneOf::Left(true)),
4923                ..Default::default()
4924            },
4925            ..Default::default()
4926        },
4927    );
4928
4929    client_a
4930        .fs()
4931        .insert_tree(
4932            path!("/root"),
4933            json!({
4934                "dir-1": {
4935                    "one.rs": "const ONE: usize = 1;",
4936                    "two.rs": "const TWO: usize = one::ONE + one::ONE;",
4937                },
4938                "dir-2": {
4939                    "three.rs": "const THREE: usize = two::TWO + one::ONE;",
4940                }
4941            }),
4942        )
4943        .await;
4944    let (project_a, worktree_id) = client_a
4945        .build_local_project(path!("/root/dir-1"), cx_a)
4946        .await;
4947    let project_id = active_call_a
4948        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
4949        .await
4950        .unwrap();
4951    let project_b = client_b.join_remote_project(project_id, cx_b).await;
4952
4953    // Open the file on client B.
4954    let (buffer_b, _handle) = project_b
4955        .update(cx_b, |p, cx| {
4956            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
4957        })
4958        .await
4959        .unwrap();
4960
4961    // Request references to a symbol as the guest.
4962    let fake_language_server = fake_language_servers.next().await.unwrap();
4963    let (lsp_response_tx, rx) = mpsc::unbounded::<Result<Option<Vec<lsp::Location>>>>();
4964    fake_language_server.set_request_handler::<lsp::request::References, _, _>({
4965        let rx = Arc::new(Mutex::new(Some(rx)));
4966        move |params, _| {
4967            assert_eq!(
4968                params.text_document_position.text_document.uri.as_str(),
4969                uri!("file:///root/dir-1/one.rs")
4970            );
4971            let rx = rx.clone();
4972            async move {
4973                let mut response_rx = rx.lock().take().unwrap();
4974                let result = response_rx.next().await.unwrap();
4975                *rx.lock() = Some(response_rx);
4976                result
4977            }
4978        }
4979    });
4980
4981    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
4982
4983    // User is informed that a request is pending.
4984    executor.run_until_parked();
4985    project_b.read_with(cx_b, |project, cx| {
4986        let status = project.language_server_statuses(cx).next().unwrap().1;
4987        assert_eq!(status.name, "my-fake-lsp-adapter");
4988        assert_eq!(
4989            status.pending_work.values().next().unwrap().message,
4990            Some("Finding references...".into())
4991        );
4992    });
4993
4994    // Cause the language server to respond.
4995    lsp_response_tx
4996        .unbounded_send(Ok(Some(vec![
4997            lsp::Location {
4998                uri: lsp::Url::from_file_path(path!("/root/dir-1/two.rs")).unwrap(),
4999                range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
5000            },
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, 35), lsp::Position::new(0, 38)),
5004            },
5005            lsp::Location {
5006                uri: lsp::Url::from_file_path(path!("/root/dir-2/three.rs")).unwrap(),
5007                range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
5008            },
5009        ])))
5010        .unwrap();
5011
5012    let references = references.await.unwrap();
5013    executor.run_until_parked();
5014    project_b.read_with(cx_b, |project, cx| {
5015        // User is informed that a request is no longer pending.
5016        let status = project.language_server_statuses(cx).next().unwrap().1;
5017        assert!(status.pending_work.is_empty());
5018
5019        assert_eq!(references.len(), 3);
5020        assert_eq!(project.worktrees(cx).count(), 2);
5021
5022        let two_buffer = references[0].buffer.read(cx);
5023        let three_buffer = references[2].buffer.read(cx);
5024        assert_eq!(
5025            two_buffer.file().unwrap().path().as_ref(),
5026            Path::new("two.rs")
5027        );
5028        assert_eq!(references[1].buffer, references[0].buffer);
5029        assert_eq!(
5030            three_buffer.file().unwrap().full_path(cx),
5031            Path::new(path!("/root/dir-2/three.rs"))
5032        );
5033
5034        assert_eq!(references[0].range.to_offset(two_buffer), 24..27);
5035        assert_eq!(references[1].range.to_offset(two_buffer), 35..38);
5036        assert_eq!(references[2].range.to_offset(three_buffer), 37..40);
5037    });
5038
5039    let references = project_b.update(cx_b, |p, cx| p.references(&buffer_b, 7, cx));
5040
5041    // User is informed that a request is pending.
5042    executor.run_until_parked();
5043    project_b.read_with(cx_b, |project, cx| {
5044        let status = project.language_server_statuses(cx).next().unwrap().1;
5045        assert_eq!(status.name, "my-fake-lsp-adapter");
5046        assert_eq!(
5047            status.pending_work.values().next().unwrap().message,
5048            Some("Finding references...".into())
5049        );
5050    });
5051
5052    // Cause the LSP request to fail.
5053    lsp_response_tx
5054        .unbounded_send(Err(anyhow!("can't find references")))
5055        .unwrap();
5056    references.await.unwrap_err();
5057
5058    // User is informed that the request is no longer pending.
5059    executor.run_until_parked();
5060    project_b.read_with(cx_b, |project, cx| {
5061        let status = project.language_server_statuses(cx).next().unwrap().1;
5062        assert!(status.pending_work.is_empty());
5063    });
5064}
5065
5066#[gpui::test(iterations = 10)]
5067async fn test_project_search(
5068    executor: BackgroundExecutor,
5069    cx_a: &mut TestAppContext,
5070    cx_b: &mut TestAppContext,
5071) {
5072    let mut server = TestServer::start(executor.clone()).await;
5073    let client_a = server.create_client(cx_a, "user_a").await;
5074    let client_b = server.create_client(cx_b, "user_b").await;
5075    server
5076        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5077        .await;
5078    let active_call_a = cx_a.read(ActiveCall::global);
5079
5080    client_a
5081        .fs()
5082        .insert_tree(
5083            "/root",
5084            json!({
5085                "dir-1": {
5086                    "a": "hello world",
5087                    "b": "goodnight moon",
5088                    "c": "a world of goo",
5089                    "d": "world champion of clown world",
5090                },
5091                "dir-2": {
5092                    "e": "disney world is fun",
5093                }
5094            }),
5095        )
5096        .await;
5097    let (project_a, _) = client_a.build_local_project("/root/dir-1", cx_a).await;
5098    let (worktree_2, _) = project_a
5099        .update(cx_a, |p, cx| {
5100            p.find_or_create_worktree("/root/dir-2", true, cx)
5101        })
5102        .await
5103        .unwrap();
5104    worktree_2
5105        .read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
5106        .await;
5107    let project_id = active_call_a
5108        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5109        .await
5110        .unwrap();
5111
5112    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5113
5114    // Perform a search as the guest.
5115    let mut results = HashMap::default();
5116    let search_rx = project_b.update(cx_b, |project, cx| {
5117        project.search(
5118            SearchQuery::text(
5119                "world",
5120                false,
5121                false,
5122                false,
5123                Default::default(),
5124                Default::default(),
5125                false,
5126                None,
5127            )
5128            .unwrap(),
5129            cx,
5130        )
5131    });
5132    while let Ok(result) = search_rx.recv().await {
5133        match result {
5134            SearchResult::Buffer { buffer, ranges } => {
5135                results.entry(buffer).or_insert(ranges);
5136            }
5137            SearchResult::LimitReached => {
5138                panic!(
5139                    "Unexpectedly reached search limit in tests. If you do want to assert limit-reached, change this panic call."
5140                )
5141            }
5142        };
5143    }
5144
5145    let mut ranges_by_path = results
5146        .into_iter()
5147        .map(|(buffer, ranges)| {
5148            buffer.read_with(cx_b, |buffer, cx| {
5149                let path = buffer.file().unwrap().full_path(cx);
5150                let offset_ranges = ranges
5151                    .into_iter()
5152                    .map(|range| range.to_offset(buffer))
5153                    .collect::<Vec<_>>();
5154                (path, offset_ranges)
5155            })
5156        })
5157        .collect::<Vec<_>>();
5158    ranges_by_path.sort_by_key(|(path, _)| path.clone());
5159
5160    assert_eq!(
5161        ranges_by_path,
5162        &[
5163            (PathBuf::from("dir-1/a"), vec![6..11]),
5164            (PathBuf::from("dir-1/c"), vec![2..7]),
5165            (PathBuf::from("dir-1/d"), vec![0..5, 24..29]),
5166            (PathBuf::from("dir-2/e"), vec![7..12]),
5167        ]
5168    );
5169}
5170
5171#[gpui::test(iterations = 10)]
5172async fn test_document_highlights(
5173    executor: BackgroundExecutor,
5174    cx_a: &mut TestAppContext,
5175    cx_b: &mut TestAppContext,
5176) {
5177    let mut server = TestServer::start(executor.clone()).await;
5178    let client_a = server.create_client(cx_a, "user_a").await;
5179    let client_b = server.create_client(cx_b, "user_b").await;
5180    server
5181        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5182        .await;
5183    let active_call_a = cx_a.read(ActiveCall::global);
5184
5185    client_a
5186        .fs()
5187        .insert_tree(
5188            path!("/root-1"),
5189            json!({
5190                "main.rs": "fn double(number: i32) -> i32 { number + number }",
5191            }),
5192        )
5193        .await;
5194
5195    let mut fake_language_servers = client_a
5196        .language_registry()
5197        .register_fake_lsp("Rust", Default::default());
5198    client_a.language_registry().add(rust_lang());
5199
5200    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5201    let project_id = active_call_a
5202        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5203        .await
5204        .unwrap();
5205    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5206
5207    // Open the file on client B.
5208    let (buffer_b, _handle) = project_b
5209        .update(cx_b, |p, cx| {
5210            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5211        })
5212        .await
5213        .unwrap();
5214
5215    // Request document highlights as the guest.
5216    let fake_language_server = fake_language_servers.next().await.unwrap();
5217    fake_language_server.set_request_handler::<lsp::request::DocumentHighlightRequest, _, _>(
5218        |params, _| async move {
5219            assert_eq!(
5220                params
5221                    .text_document_position_params
5222                    .text_document
5223                    .uri
5224                    .as_str(),
5225                uri!("file:///root-1/main.rs")
5226            );
5227            assert_eq!(
5228                params.text_document_position_params.position,
5229                lsp::Position::new(0, 34)
5230            );
5231            Ok(Some(vec![
5232                lsp::DocumentHighlight {
5233                    kind: Some(lsp::DocumentHighlightKind::WRITE),
5234                    range: lsp::Range::new(lsp::Position::new(0, 10), lsp::Position::new(0, 16)),
5235                },
5236                lsp::DocumentHighlight {
5237                    kind: Some(lsp::DocumentHighlightKind::READ),
5238                    range: lsp::Range::new(lsp::Position::new(0, 32), lsp::Position::new(0, 38)),
5239                },
5240                lsp::DocumentHighlight {
5241                    kind: Some(lsp::DocumentHighlightKind::READ),
5242                    range: lsp::Range::new(lsp::Position::new(0, 41), lsp::Position::new(0, 47)),
5243                },
5244            ]))
5245        },
5246    );
5247
5248    let highlights = project_b
5249        .update(cx_b, |p, cx| p.document_highlights(&buffer_b, 34, cx))
5250        .await
5251        .unwrap();
5252
5253    buffer_b.read_with(cx_b, |buffer, _| {
5254        let snapshot = buffer.snapshot();
5255
5256        let highlights = highlights
5257            .into_iter()
5258            .map(|highlight| (highlight.kind, highlight.range.to_offset(&snapshot)))
5259            .collect::<Vec<_>>();
5260        assert_eq!(
5261            highlights,
5262            &[
5263                (lsp::DocumentHighlightKind::WRITE, 10..16),
5264                (lsp::DocumentHighlightKind::READ, 32..38),
5265                (lsp::DocumentHighlightKind::READ, 41..47)
5266            ]
5267        )
5268    });
5269}
5270
5271#[gpui::test(iterations = 10)]
5272async fn test_lsp_hover(
5273    executor: BackgroundExecutor,
5274    cx_a: &mut TestAppContext,
5275    cx_b: &mut TestAppContext,
5276) {
5277    let mut server = TestServer::start(executor.clone()).await;
5278    let client_a = server.create_client(cx_a, "user_a").await;
5279    let client_b = server.create_client(cx_b, "user_b").await;
5280    server
5281        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5282        .await;
5283    let active_call_a = cx_a.read(ActiveCall::global);
5284
5285    client_a
5286        .fs()
5287        .insert_tree(
5288            path!("/root-1"),
5289            json!({
5290                "main.rs": "use std::collections::HashMap;",
5291            }),
5292        )
5293        .await;
5294
5295    client_a.language_registry().add(rust_lang());
5296    let language_server_names = ["rust-analyzer", "CrabLang-ls"];
5297    let mut language_servers = [
5298        client_a.language_registry().register_fake_lsp(
5299            "Rust",
5300            FakeLspAdapter {
5301                name: "rust-analyzer",
5302                capabilities: lsp::ServerCapabilities {
5303                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5304                    ..lsp::ServerCapabilities::default()
5305                },
5306                ..FakeLspAdapter::default()
5307            },
5308        ),
5309        client_a.language_registry().register_fake_lsp(
5310            "Rust",
5311            FakeLspAdapter {
5312                name: "CrabLang-ls",
5313                capabilities: lsp::ServerCapabilities {
5314                    hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
5315                    ..lsp::ServerCapabilities::default()
5316                },
5317                ..FakeLspAdapter::default()
5318            },
5319        ),
5320    ];
5321
5322    let (project_a, worktree_id) = client_a.build_local_project(path!("/root-1"), cx_a).await;
5323    let project_id = active_call_a
5324        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5325        .await
5326        .unwrap();
5327    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5328
5329    // Open the file as the guest
5330    let (buffer_b, _handle) = project_b
5331        .update(cx_b, |p, cx| {
5332            p.open_buffer_with_lsp((worktree_id, "main.rs"), cx)
5333        })
5334        .await
5335        .unwrap();
5336
5337    let mut servers_with_hover_requests = HashMap::default();
5338    for i in 0..language_server_names.len() {
5339        let new_server = language_servers[i].next().await.unwrap_or_else(|| {
5340            panic!(
5341                "Failed to get language server #{i} with name {}",
5342                &language_server_names[i]
5343            )
5344        });
5345        let new_server_name = new_server.server.name();
5346        assert!(
5347            !servers_with_hover_requests.contains_key(&new_server_name),
5348            "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`"
5349        );
5350        match new_server_name.as_ref() {
5351            "CrabLang-ls" => {
5352                servers_with_hover_requests.insert(
5353                    new_server_name.clone(),
5354                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5355                        move |params, _| {
5356                            assert_eq!(
5357                                params
5358                                    .text_document_position_params
5359                                    .text_document
5360                                    .uri
5361                                    .as_str(),
5362                                uri!("file:///root-1/main.rs")
5363                            );
5364                            let name = new_server_name.clone();
5365                            async move {
5366                                Ok(Some(lsp::Hover {
5367                                    contents: lsp::HoverContents::Scalar(
5368                                        lsp::MarkedString::String(format!("{name} hover")),
5369                                    ),
5370                                    range: None,
5371                                }))
5372                            }
5373                        },
5374                    ),
5375                );
5376            }
5377            "rust-analyzer" => {
5378                servers_with_hover_requests.insert(
5379                    new_server_name.clone(),
5380                    new_server.set_request_handler::<lsp::request::HoverRequest, _, _>(
5381                        |params, _| async move {
5382                            assert_eq!(
5383                                params
5384                                    .text_document_position_params
5385                                    .text_document
5386                                    .uri
5387                                    .as_str(),
5388                                uri!("file:///root-1/main.rs")
5389                            );
5390                            assert_eq!(
5391                                params.text_document_position_params.position,
5392                                lsp::Position::new(0, 22)
5393                            );
5394                            Ok(Some(lsp::Hover {
5395                                contents: lsp::HoverContents::Array(vec![
5396                                    lsp::MarkedString::String("Test hover content.".to_string()),
5397                                    lsp::MarkedString::LanguageString(lsp::LanguageString {
5398                                        language: "Rust".to_string(),
5399                                        value: "let foo = 42;".to_string(),
5400                                    }),
5401                                ]),
5402                                range: Some(lsp::Range::new(
5403                                    lsp::Position::new(0, 22),
5404                                    lsp::Position::new(0, 29),
5405                                )),
5406                            }))
5407                        },
5408                    ),
5409                );
5410            }
5411            unexpected => panic!("Unexpected server name: {unexpected}"),
5412        }
5413    }
5414
5415    // Request hover information as the guest.
5416    let mut hovers = project_b
5417        .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx))
5418        .await;
5419    assert_eq!(
5420        hovers.len(),
5421        2,
5422        "Expected two hovers from both language servers, but got: {hovers:?}"
5423    );
5424
5425    let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map(
5426        |mut hover_request| async move {
5427            hover_request
5428                .next()
5429                .await
5430                .expect("All hover requests should have been triggered")
5431        },
5432    ))
5433    .await;
5434
5435    hovers.sort_by_key(|hover| hover.contents.len());
5436    let first_hover = hovers.first().cloned().unwrap();
5437    assert_eq!(
5438        first_hover.contents,
5439        vec![project::HoverBlock {
5440            text: "CrabLang-ls hover".to_string(),
5441            kind: HoverBlockKind::Markdown,
5442        },]
5443    );
5444    let second_hover = hovers.last().cloned().unwrap();
5445    assert_eq!(
5446        second_hover.contents,
5447        vec![
5448            project::HoverBlock {
5449                text: "Test hover content.".to_string(),
5450                kind: HoverBlockKind::Markdown,
5451            },
5452            project::HoverBlock {
5453                text: "let foo = 42;".to_string(),
5454                kind: HoverBlockKind::Code {
5455                    language: "Rust".to_string()
5456                },
5457            }
5458        ]
5459    );
5460    buffer_b.read_with(cx_b, |buffer, _| {
5461        let snapshot = buffer.snapshot();
5462        assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29);
5463    });
5464}
5465
5466#[gpui::test(iterations = 10)]
5467async fn test_project_symbols(
5468    executor: BackgroundExecutor,
5469    cx_a: &mut TestAppContext,
5470    cx_b: &mut TestAppContext,
5471) {
5472    let mut server = TestServer::start(executor.clone()).await;
5473    let client_a = server.create_client(cx_a, "user_a").await;
5474    let client_b = server.create_client(cx_b, "user_b").await;
5475    server
5476        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5477        .await;
5478    let active_call_a = cx_a.read(ActiveCall::global);
5479
5480    client_a.language_registry().add(rust_lang());
5481    let mut fake_language_servers = client_a.language_registry().register_fake_lsp(
5482        "Rust",
5483        FakeLspAdapter {
5484            capabilities: lsp::ServerCapabilities {
5485                workspace_symbol_provider: Some(OneOf::Left(true)),
5486                ..Default::default()
5487            },
5488            ..Default::default()
5489        },
5490    );
5491
5492    client_a
5493        .fs()
5494        .insert_tree(
5495            path!("/code"),
5496            json!({
5497                "crate-1": {
5498                    "one.rs": "const ONE: usize = 1;",
5499                },
5500                "crate-2": {
5501                    "two.rs": "const TWO: usize = 2; const THREE: usize = 3;",
5502                },
5503                "private": {
5504                    "passwords.txt": "the-password",
5505                }
5506            }),
5507        )
5508        .await;
5509    let (project_a, worktree_id) = client_a
5510        .build_local_project(path!("/code/crate-1"), cx_a)
5511        .await;
5512    let project_id = active_call_a
5513        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5514        .await
5515        .unwrap();
5516    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5517
5518    // Cause the language server to start.
5519    let _buffer = project_b
5520        .update(cx_b, |p, cx| {
5521            p.open_buffer_with_lsp((worktree_id, "one.rs"), cx)
5522        })
5523        .await
5524        .unwrap();
5525
5526    let fake_language_server = fake_language_servers.next().await.unwrap();
5527    fake_language_server.set_request_handler::<lsp::WorkspaceSymbolRequest, _, _>(
5528        |_, _| async move {
5529            Ok(Some(lsp::WorkspaceSymbolResponse::Flat(vec![
5530                #[allow(deprecated)]
5531                lsp::SymbolInformation {
5532                    name: "TWO".into(),
5533                    location: lsp::Location {
5534                        uri: lsp::Url::from_file_path(path!("/code/crate-2/two.rs")).unwrap(),
5535                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5536                    },
5537                    kind: lsp::SymbolKind::CONSTANT,
5538                    tags: None,
5539                    container_name: None,
5540                    deprecated: None,
5541                },
5542            ])))
5543        },
5544    );
5545
5546    // Request the definition of a symbol as the guest.
5547    let symbols = project_b
5548        .update(cx_b, |p, cx| p.symbols("two", cx))
5549        .await
5550        .unwrap();
5551    assert_eq!(symbols.len(), 1);
5552    assert_eq!(symbols[0].name, "TWO");
5553
5554    // Open one of the returned symbols.
5555    let buffer_b_2 = project_b
5556        .update(cx_b, |project, cx| {
5557            project.open_buffer_for_symbol(&symbols[0], cx)
5558        })
5559        .await
5560        .unwrap();
5561
5562    buffer_b_2.read_with(cx_b, |buffer, cx| {
5563        assert_eq!(
5564            buffer.file().unwrap().full_path(cx),
5565            Path::new(path!("/code/crate-2/two.rs"))
5566        );
5567    });
5568
5569    // Attempt to craft a symbol and violate host's privacy by opening an arbitrary file.
5570    let mut fake_symbol = symbols[0].clone();
5571    fake_symbol.path.path = Path::new(path!("/code/secrets")).into();
5572    let error = project_b
5573        .update(cx_b, |project, cx| {
5574            project.open_buffer_for_symbol(&fake_symbol, cx)
5575        })
5576        .await
5577        .unwrap_err();
5578    assert!(error.to_string().contains("invalid symbol signature"));
5579}
5580
5581#[gpui::test(iterations = 10)]
5582async fn test_open_buffer_while_getting_definition_pointing_to_it(
5583    executor: BackgroundExecutor,
5584    cx_a: &mut TestAppContext,
5585    cx_b: &mut TestAppContext,
5586    mut rng: StdRng,
5587) {
5588    let mut server = TestServer::start(executor.clone()).await;
5589    let client_a = server.create_client(cx_a, "user_a").await;
5590    let client_b = server.create_client(cx_b, "user_b").await;
5591    server
5592        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
5593        .await;
5594    let active_call_a = cx_a.read(ActiveCall::global);
5595
5596    client_a.language_registry().add(rust_lang());
5597    let mut fake_language_servers = client_a
5598        .language_registry()
5599        .register_fake_lsp("Rust", Default::default());
5600
5601    client_a
5602        .fs()
5603        .insert_tree(
5604            path!("/root"),
5605            json!({
5606                "a.rs": "const ONE: usize = b::TWO;",
5607                "b.rs": "const TWO: usize = 2",
5608            }),
5609        )
5610        .await;
5611    let (project_a, worktree_id) = client_a.build_local_project(path!("/root"), cx_a).await;
5612    let project_id = active_call_a
5613        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
5614        .await
5615        .unwrap();
5616    let project_b = client_b.join_remote_project(project_id, cx_b).await;
5617
5618    let (buffer_b1, _lsp) = project_b
5619        .update(cx_b, |p, cx| {
5620            p.open_buffer_with_lsp((worktree_id, "a.rs"), cx)
5621        })
5622        .await
5623        .unwrap();
5624
5625    let fake_language_server = fake_language_servers.next().await.unwrap();
5626    fake_language_server.set_request_handler::<lsp::request::GotoDefinition, _, _>(
5627        |_, _| async move {
5628            Ok(Some(lsp::GotoDefinitionResponse::Scalar(
5629                lsp::Location::new(
5630                    lsp::Url::from_file_path(path!("/root/b.rs")).unwrap(),
5631                    lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
5632                ),
5633            )))
5634        },
5635    );
5636
5637    let definitions;
5638    let buffer_b2;
5639    if rng.r#gen() {
5640        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5641        (buffer_b2, _) = project_b
5642            .update(cx_b, |p, cx| {
5643                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5644            })
5645            .await
5646            .unwrap();
5647    } else {
5648        (buffer_b2, _) = project_b
5649            .update(cx_b, |p, cx| {
5650                p.open_buffer_with_lsp((worktree_id, "b.rs"), cx)
5651            })
5652            .await
5653            .unwrap();
5654        definitions = project_b.update(cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
5655    }
5656
5657    let definitions = definitions.await.unwrap();
5658    assert_eq!(definitions.len(), 1);
5659    assert_eq!(definitions[0].target.buffer, buffer_b2);
5660}
5661
5662#[gpui::test(iterations = 10)]
5663async fn test_contacts(
5664    executor: BackgroundExecutor,
5665    cx_a: &mut TestAppContext,
5666    cx_b: &mut TestAppContext,
5667    cx_c: &mut TestAppContext,
5668    cx_d: &mut TestAppContext,
5669) {
5670    let mut server = TestServer::start(executor.clone()).await;
5671    let client_a = server.create_client(cx_a, "user_a").await;
5672    let client_b = server.create_client(cx_b, "user_b").await;
5673    let client_c = server.create_client(cx_c, "user_c").await;
5674    let client_d = server.create_client(cx_d, "user_d").await;
5675    server
5676        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
5677        .await;
5678    let active_call_a = cx_a.read(ActiveCall::global);
5679    let active_call_b = cx_b.read(ActiveCall::global);
5680    let active_call_c = cx_c.read(ActiveCall::global);
5681    let _active_call_d = cx_d.read(ActiveCall::global);
5682
5683    executor.run_until_parked();
5684    assert_eq!(
5685        contacts(&client_a, cx_a),
5686        [
5687            ("user_b".to_string(), "online", "free"),
5688            ("user_c".to_string(), "online", "free")
5689        ]
5690    );
5691    assert_eq!(
5692        contacts(&client_b, cx_b),
5693        [
5694            ("user_a".to_string(), "online", "free"),
5695            ("user_c".to_string(), "online", "free")
5696        ]
5697    );
5698    assert_eq!(
5699        contacts(&client_c, cx_c),
5700        [
5701            ("user_a".to_string(), "online", "free"),
5702            ("user_b".to_string(), "online", "free")
5703        ]
5704    );
5705    assert_eq!(contacts(&client_d, cx_d), []);
5706
5707    server.disconnect_client(client_c.peer_id().unwrap());
5708    server.forbid_connections();
5709    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
5710    assert_eq!(
5711        contacts(&client_a, cx_a),
5712        [
5713            ("user_b".to_string(), "online", "free"),
5714            ("user_c".to_string(), "offline", "free")
5715        ]
5716    );
5717    assert_eq!(
5718        contacts(&client_b, cx_b),
5719        [
5720            ("user_a".to_string(), "online", "free"),
5721            ("user_c".to_string(), "offline", "free")
5722        ]
5723    );
5724    assert_eq!(contacts(&client_c, cx_c), []);
5725    assert_eq!(contacts(&client_d, cx_d), []);
5726
5727    server.allow_connections();
5728    client_c
5729        .authenticate_and_connect(false, &cx_c.to_async())
5730        .await
5731        .into_response()
5732        .unwrap();
5733
5734    executor.run_until_parked();
5735    assert_eq!(
5736        contacts(&client_a, cx_a),
5737        [
5738            ("user_b".to_string(), "online", "free"),
5739            ("user_c".to_string(), "online", "free")
5740        ]
5741    );
5742    assert_eq!(
5743        contacts(&client_b, cx_b),
5744        [
5745            ("user_a".to_string(), "online", "free"),
5746            ("user_c".to_string(), "online", "free")
5747        ]
5748    );
5749    assert_eq!(
5750        contacts(&client_c, cx_c),
5751        [
5752            ("user_a".to_string(), "online", "free"),
5753            ("user_b".to_string(), "online", "free")
5754        ]
5755    );
5756    assert_eq!(contacts(&client_d, cx_d), []);
5757
5758    active_call_a
5759        .update(cx_a, |call, cx| {
5760            call.invite(client_b.user_id().unwrap(), None, cx)
5761        })
5762        .await
5763        .unwrap();
5764    executor.run_until_parked();
5765    assert_eq!(
5766        contacts(&client_a, cx_a),
5767        [
5768            ("user_b".to_string(), "online", "busy"),
5769            ("user_c".to_string(), "online", "free")
5770        ]
5771    );
5772    assert_eq!(
5773        contacts(&client_b, cx_b),
5774        [
5775            ("user_a".to_string(), "online", "busy"),
5776            ("user_c".to_string(), "online", "free")
5777        ]
5778    );
5779    assert_eq!(
5780        contacts(&client_c, cx_c),
5781        [
5782            ("user_a".to_string(), "online", "busy"),
5783            ("user_b".to_string(), "online", "busy")
5784        ]
5785    );
5786    assert_eq!(contacts(&client_d, cx_d), []);
5787
5788    // Client B and client D become contacts while client B is being called.
5789    server
5790        .make_contacts(&mut [(&client_b, cx_b), (&client_d, cx_d)])
5791        .await;
5792    executor.run_until_parked();
5793    assert_eq!(
5794        contacts(&client_a, cx_a),
5795        [
5796            ("user_b".to_string(), "online", "busy"),
5797            ("user_c".to_string(), "online", "free")
5798        ]
5799    );
5800    assert_eq!(
5801        contacts(&client_b, cx_b),
5802        [
5803            ("user_a".to_string(), "online", "busy"),
5804            ("user_c".to_string(), "online", "free"),
5805            ("user_d".to_string(), "online", "free"),
5806        ]
5807    );
5808    assert_eq!(
5809        contacts(&client_c, cx_c),
5810        [
5811            ("user_a".to_string(), "online", "busy"),
5812            ("user_b".to_string(), "online", "busy")
5813        ]
5814    );
5815    assert_eq!(
5816        contacts(&client_d, cx_d),
5817        [("user_b".to_string(), "online", "busy")]
5818    );
5819
5820    active_call_b.update(cx_b, |call, cx| call.decline_incoming(cx).unwrap());
5821    executor.run_until_parked();
5822    assert_eq!(
5823        contacts(&client_a, cx_a),
5824        [
5825            ("user_b".to_string(), "online", "free"),
5826            ("user_c".to_string(), "online", "free")
5827        ]
5828    );
5829    assert_eq!(
5830        contacts(&client_b, cx_b),
5831        [
5832            ("user_a".to_string(), "online", "free"),
5833            ("user_c".to_string(), "online", "free"),
5834            ("user_d".to_string(), "online", "free")
5835        ]
5836    );
5837    assert_eq!(
5838        contacts(&client_c, cx_c),
5839        [
5840            ("user_a".to_string(), "online", "free"),
5841            ("user_b".to_string(), "online", "free")
5842        ]
5843    );
5844    assert_eq!(
5845        contacts(&client_d, cx_d),
5846        [("user_b".to_string(), "online", "free")]
5847    );
5848
5849    active_call_c
5850        .update(cx_c, |call, cx| {
5851            call.invite(client_a.user_id().unwrap(), None, cx)
5852        })
5853        .await
5854        .unwrap();
5855    executor.run_until_parked();
5856    assert_eq!(
5857        contacts(&client_a, cx_a),
5858        [
5859            ("user_b".to_string(), "online", "free"),
5860            ("user_c".to_string(), "online", "busy")
5861        ]
5862    );
5863    assert_eq!(
5864        contacts(&client_b, cx_b),
5865        [
5866            ("user_a".to_string(), "online", "busy"),
5867            ("user_c".to_string(), "online", "busy"),
5868            ("user_d".to_string(), "online", "free")
5869        ]
5870    );
5871    assert_eq!(
5872        contacts(&client_c, cx_c),
5873        [
5874            ("user_a".to_string(), "online", "busy"),
5875            ("user_b".to_string(), "online", "free")
5876        ]
5877    );
5878    assert_eq!(
5879        contacts(&client_d, cx_d),
5880        [("user_b".to_string(), "online", "free")]
5881    );
5882
5883    active_call_a
5884        .update(cx_a, |call, cx| call.accept_incoming(cx))
5885        .await
5886        .unwrap();
5887    executor.run_until_parked();
5888    assert_eq!(
5889        contacts(&client_a, cx_a),
5890        [
5891            ("user_b".to_string(), "online", "free"),
5892            ("user_c".to_string(), "online", "busy")
5893        ]
5894    );
5895    assert_eq!(
5896        contacts(&client_b, cx_b),
5897        [
5898            ("user_a".to_string(), "online", "busy"),
5899            ("user_c".to_string(), "online", "busy"),
5900            ("user_d".to_string(), "online", "free")
5901        ]
5902    );
5903    assert_eq!(
5904        contacts(&client_c, cx_c),
5905        [
5906            ("user_a".to_string(), "online", "busy"),
5907            ("user_b".to_string(), "online", "free")
5908        ]
5909    );
5910    assert_eq!(
5911        contacts(&client_d, cx_d),
5912        [("user_b".to_string(), "online", "free")]
5913    );
5914
5915    active_call_a
5916        .update(cx_a, |call, cx| {
5917            call.invite(client_b.user_id().unwrap(), None, cx)
5918        })
5919        .await
5920        .unwrap();
5921    executor.run_until_parked();
5922    assert_eq!(
5923        contacts(&client_a, cx_a),
5924        [
5925            ("user_b".to_string(), "online", "busy"),
5926            ("user_c".to_string(), "online", "busy")
5927        ]
5928    );
5929    assert_eq!(
5930        contacts(&client_b, cx_b),
5931        [
5932            ("user_a".to_string(), "online", "busy"),
5933            ("user_c".to_string(), "online", "busy"),
5934            ("user_d".to_string(), "online", "free")
5935        ]
5936    );
5937    assert_eq!(
5938        contacts(&client_c, cx_c),
5939        [
5940            ("user_a".to_string(), "online", "busy"),
5941            ("user_b".to_string(), "online", "busy")
5942        ]
5943    );
5944    assert_eq!(
5945        contacts(&client_d, cx_d),
5946        [("user_b".to_string(), "online", "busy")]
5947    );
5948
5949    active_call_a
5950        .update(cx_a, |call, cx| call.hang_up(cx))
5951        .await
5952        .unwrap();
5953    executor.run_until_parked();
5954    assert_eq!(
5955        contacts(&client_a, cx_a),
5956        [
5957            ("user_b".to_string(), "online", "free"),
5958            ("user_c".to_string(), "online", "free")
5959        ]
5960    );
5961    assert_eq!(
5962        contacts(&client_b, cx_b),
5963        [
5964            ("user_a".to_string(), "online", "free"),
5965            ("user_c".to_string(), "online", "free"),
5966            ("user_d".to_string(), "online", "free")
5967        ]
5968    );
5969    assert_eq!(
5970        contacts(&client_c, cx_c),
5971        [
5972            ("user_a".to_string(), "online", "free"),
5973            ("user_b".to_string(), "online", "free")
5974        ]
5975    );
5976    assert_eq!(
5977        contacts(&client_d, cx_d),
5978        [("user_b".to_string(), "online", "free")]
5979    );
5980
5981    active_call_a
5982        .update(cx_a, |call, cx| {
5983            call.invite(client_b.user_id().unwrap(), None, cx)
5984        })
5985        .await
5986        .unwrap();
5987    executor.run_until_parked();
5988    assert_eq!(
5989        contacts(&client_a, cx_a),
5990        [
5991            ("user_b".to_string(), "online", "busy"),
5992            ("user_c".to_string(), "online", "free")
5993        ]
5994    );
5995    assert_eq!(
5996        contacts(&client_b, cx_b),
5997        [
5998            ("user_a".to_string(), "online", "busy"),
5999            ("user_c".to_string(), "online", "free"),
6000            ("user_d".to_string(), "online", "free")
6001        ]
6002    );
6003    assert_eq!(
6004        contacts(&client_c, cx_c),
6005        [
6006            ("user_a".to_string(), "online", "busy"),
6007            ("user_b".to_string(), "online", "busy")
6008        ]
6009    );
6010    assert_eq!(
6011        contacts(&client_d, cx_d),
6012        [("user_b".to_string(), "online", "busy")]
6013    );
6014
6015    server.forbid_connections();
6016    server.disconnect_client(client_a.peer_id().unwrap());
6017    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6018    assert_eq!(contacts(&client_a, cx_a), []);
6019    assert_eq!(
6020        contacts(&client_b, cx_b),
6021        [
6022            ("user_a".to_string(), "offline", "free"),
6023            ("user_c".to_string(), "online", "free"),
6024            ("user_d".to_string(), "online", "free")
6025        ]
6026    );
6027    assert_eq!(
6028        contacts(&client_c, cx_c),
6029        [
6030            ("user_a".to_string(), "offline", "free"),
6031            ("user_b".to_string(), "online", "free")
6032        ]
6033    );
6034    assert_eq!(
6035        contacts(&client_d, cx_d),
6036        [("user_b".to_string(), "online", "free")]
6037    );
6038
6039    // Test removing a contact
6040    client_b
6041        .user_store()
6042        .update(cx_b, |store, cx| {
6043            store.remove_contact(client_c.user_id().unwrap(), cx)
6044        })
6045        .await
6046        .unwrap();
6047    executor.run_until_parked();
6048    assert_eq!(
6049        contacts(&client_b, cx_b),
6050        [
6051            ("user_a".to_string(), "offline", "free"),
6052            ("user_d".to_string(), "online", "free")
6053        ]
6054    );
6055    assert_eq!(
6056        contacts(&client_c, cx_c),
6057        [("user_a".to_string(), "offline", "free"),]
6058    );
6059
6060    fn contacts(
6061        client: &TestClient,
6062        cx: &TestAppContext,
6063    ) -> Vec<(String, &'static str, &'static str)> {
6064        client.user_store().read_with(cx, |store, _| {
6065            store
6066                .contacts()
6067                .iter()
6068                .map(|contact| {
6069                    (
6070                        contact.user.github_login.clone(),
6071                        if contact.online { "online" } else { "offline" },
6072                        if contact.busy { "busy" } else { "free" },
6073                    )
6074                })
6075                .collect()
6076        })
6077    }
6078}
6079
6080#[gpui::test(iterations = 10)]
6081async fn test_contact_requests(
6082    executor: BackgroundExecutor,
6083    cx_a: &mut TestAppContext,
6084    cx_a2: &mut TestAppContext,
6085    cx_b: &mut TestAppContext,
6086    cx_b2: &mut TestAppContext,
6087    cx_c: &mut TestAppContext,
6088    cx_c2: &mut TestAppContext,
6089) {
6090    // Connect to a server as 3 clients.
6091    let mut server = TestServer::start(executor.clone()).await;
6092    let client_a = server.create_client(cx_a, "user_a").await;
6093    let client_a2 = server.create_client(cx_a2, "user_a").await;
6094    let client_b = server.create_client(cx_b, "user_b").await;
6095    let client_b2 = server.create_client(cx_b2, "user_b").await;
6096    let client_c = server.create_client(cx_c, "user_c").await;
6097    let client_c2 = server.create_client(cx_c2, "user_c").await;
6098
6099    assert_eq!(client_a.user_id().unwrap(), client_a2.user_id().unwrap());
6100    assert_eq!(client_b.user_id().unwrap(), client_b2.user_id().unwrap());
6101    assert_eq!(client_c.user_id().unwrap(), client_c2.user_id().unwrap());
6102
6103    // User A and User C request that user B become their contact.
6104    client_a
6105        .user_store()
6106        .update(cx_a, |store, cx| {
6107            store.request_contact(client_b.user_id().unwrap(), cx)
6108        })
6109        .await
6110        .unwrap();
6111    client_c
6112        .user_store()
6113        .update(cx_c, |store, cx| {
6114            store.request_contact(client_b.user_id().unwrap(), cx)
6115        })
6116        .await
6117        .unwrap();
6118    executor.run_until_parked();
6119
6120    // All users see the pending request appear in all their clients.
6121    assert_eq!(
6122        client_a.summarize_contacts(cx_a).outgoing_requests,
6123        &["user_b"]
6124    );
6125    assert_eq!(
6126        client_a2.summarize_contacts(cx_a2).outgoing_requests,
6127        &["user_b"]
6128    );
6129    assert_eq!(
6130        client_b.summarize_contacts(cx_b).incoming_requests,
6131        &["user_a", "user_c"]
6132    );
6133    assert_eq!(
6134        client_b2.summarize_contacts(cx_b2).incoming_requests,
6135        &["user_a", "user_c"]
6136    );
6137    assert_eq!(
6138        client_c.summarize_contacts(cx_c).outgoing_requests,
6139        &["user_b"]
6140    );
6141    assert_eq!(
6142        client_c2.summarize_contacts(cx_c2).outgoing_requests,
6143        &["user_b"]
6144    );
6145
6146    // Contact requests are present upon connecting (tested here via disconnect/reconnect)
6147    disconnect_and_reconnect(&client_a, cx_a).await;
6148    disconnect_and_reconnect(&client_b, cx_b).await;
6149    disconnect_and_reconnect(&client_c, cx_c).await;
6150    executor.run_until_parked();
6151    assert_eq!(
6152        client_a.summarize_contacts(cx_a).outgoing_requests,
6153        &["user_b"]
6154    );
6155    assert_eq!(
6156        client_b.summarize_contacts(cx_b).incoming_requests,
6157        &["user_a", "user_c"]
6158    );
6159    assert_eq!(
6160        client_c.summarize_contacts(cx_c).outgoing_requests,
6161        &["user_b"]
6162    );
6163
6164    // User B accepts the request from user A.
6165    client_b
6166        .user_store()
6167        .update(cx_b, |store, cx| {
6168            store.respond_to_contact_request(client_a.user_id().unwrap(), true, cx)
6169        })
6170        .await
6171        .unwrap();
6172
6173    executor.run_until_parked();
6174
6175    // User B sees user A as their contact now in all client, and the incoming request from them is removed.
6176    let contacts_b = client_b.summarize_contacts(cx_b);
6177    assert_eq!(contacts_b.current, &["user_a"]);
6178    assert_eq!(contacts_b.incoming_requests, &["user_c"]);
6179    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6180    assert_eq!(contacts_b2.current, &["user_a"]);
6181    assert_eq!(contacts_b2.incoming_requests, &["user_c"]);
6182
6183    // User A sees user B as their contact now in all clients, and the outgoing request to them is removed.
6184    let contacts_a = client_a.summarize_contacts(cx_a);
6185    assert_eq!(contacts_a.current, &["user_b"]);
6186    assert!(contacts_a.outgoing_requests.is_empty());
6187    let contacts_a2 = client_a2.summarize_contacts(cx_a2);
6188    assert_eq!(contacts_a2.current, &["user_b"]);
6189    assert!(contacts_a2.outgoing_requests.is_empty());
6190
6191    // Contacts are present upon connecting (tested here via disconnect/reconnect)
6192    disconnect_and_reconnect(&client_a, cx_a).await;
6193    disconnect_and_reconnect(&client_b, cx_b).await;
6194    disconnect_and_reconnect(&client_c, cx_c).await;
6195    executor.run_until_parked();
6196    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6197    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6198    assert_eq!(
6199        client_b.summarize_contacts(cx_b).incoming_requests,
6200        &["user_c"]
6201    );
6202    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6203    assert_eq!(
6204        client_c.summarize_contacts(cx_c).outgoing_requests,
6205        &["user_b"]
6206    );
6207
6208    // User B rejects the request from user C.
6209    client_b
6210        .user_store()
6211        .update(cx_b, |store, cx| {
6212            store.respond_to_contact_request(client_c.user_id().unwrap(), false, cx)
6213        })
6214        .await
6215        .unwrap();
6216
6217    executor.run_until_parked();
6218
6219    // User B doesn't see user C as their contact, and the incoming request from them is removed.
6220    let contacts_b = client_b.summarize_contacts(cx_b);
6221    assert_eq!(contacts_b.current, &["user_a"]);
6222    assert!(contacts_b.incoming_requests.is_empty());
6223    let contacts_b2 = client_b2.summarize_contacts(cx_b2);
6224    assert_eq!(contacts_b2.current, &["user_a"]);
6225    assert!(contacts_b2.incoming_requests.is_empty());
6226
6227    // User C doesn't see user B as their contact, and the outgoing request to them is removed.
6228    let contacts_c = client_c.summarize_contacts(cx_c);
6229    assert!(contacts_c.current.is_empty());
6230    assert!(contacts_c.outgoing_requests.is_empty());
6231    let contacts_c2 = client_c2.summarize_contacts(cx_c2);
6232    assert!(contacts_c2.current.is_empty());
6233    assert!(contacts_c2.outgoing_requests.is_empty());
6234
6235    // Incoming/outgoing requests are not present upon connecting (tested here via disconnect/reconnect)
6236    disconnect_and_reconnect(&client_a, cx_a).await;
6237    disconnect_and_reconnect(&client_b, cx_b).await;
6238    disconnect_and_reconnect(&client_c, cx_c).await;
6239    executor.run_until_parked();
6240    assert_eq!(client_a.summarize_contacts(cx_a).current, &["user_b"]);
6241    assert_eq!(client_b.summarize_contacts(cx_b).current, &["user_a"]);
6242    assert!(
6243        client_b
6244            .summarize_contacts(cx_b)
6245            .incoming_requests
6246            .is_empty()
6247    );
6248    assert!(client_c.summarize_contacts(cx_c).current.is_empty());
6249    assert!(
6250        client_c
6251            .summarize_contacts(cx_c)
6252            .outgoing_requests
6253            .is_empty()
6254    );
6255
6256    async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
6257        client.disconnect(&cx.to_async());
6258        client.clear_contacts(cx).await;
6259        client
6260            .authenticate_and_connect(false, &cx.to_async())
6261            .await
6262            .into_response()
6263            .unwrap();
6264    }
6265}
6266
6267#[gpui::test(iterations = 10)]
6268async fn test_join_call_after_screen_was_shared(
6269    executor: BackgroundExecutor,
6270    cx_a: &mut TestAppContext,
6271    cx_b: &mut TestAppContext,
6272) {
6273    let mut server = TestServer::start(executor.clone()).await;
6274
6275    let client_a = server.create_client(cx_a, "user_a").await;
6276    let client_b = server.create_client(cx_b, "user_b").await;
6277    server
6278        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6279        .await;
6280
6281    let active_call_a = cx_a.read(ActiveCall::global);
6282    let active_call_b = cx_b.read(ActiveCall::global);
6283
6284    // Call users B and C from client A.
6285    active_call_a
6286        .update(cx_a, |call, cx| {
6287            call.invite(client_b.user_id().unwrap(), None, cx)
6288        })
6289        .await
6290        .unwrap();
6291
6292    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
6293    executor.run_until_parked();
6294    assert_eq!(
6295        room_participants(&room_a, cx_a),
6296        RoomParticipants {
6297            remote: Default::default(),
6298            pending: vec!["user_b".to_string()]
6299        }
6300    );
6301
6302    // User B receives the call.
6303
6304    let mut incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
6305    let call_b = incoming_call_b.next().await.unwrap().unwrap();
6306    assert_eq!(call_b.calling_user.github_login, "user_a");
6307
6308    // User A shares their screen
6309    let display = gpui::TestScreenCaptureSource::new();
6310    cx_a.set_screen_capture_sources(vec![display]);
6311    active_call_a
6312        .update(cx_a, |call, cx| {
6313            call.room()
6314                .unwrap()
6315                .update(cx, |room, cx| room.share_screen(cx))
6316        })
6317        .await
6318        .unwrap();
6319
6320    client_b.user_store().update(cx_b, |user_store, _| {
6321        user_store.clear_cache();
6322    });
6323
6324    // User B joins the room
6325    active_call_b
6326        .update(cx_b, |call, cx| call.accept_incoming(cx))
6327        .await
6328        .unwrap();
6329
6330    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
6331    assert!(incoming_call_b.next().await.unwrap().is_none());
6332
6333    executor.run_until_parked();
6334    assert_eq!(
6335        room_participants(&room_a, cx_a),
6336        RoomParticipants {
6337            remote: vec!["user_b".to_string()],
6338            pending: vec![],
6339        }
6340    );
6341    assert_eq!(
6342        room_participants(&room_b, cx_b),
6343        RoomParticipants {
6344            remote: vec!["user_a".to_string()],
6345            pending: vec![],
6346        }
6347    );
6348
6349    // Ensure User B sees User A's screenshare.
6350
6351    room_b.read_with(cx_b, |room, _| {
6352        assert_eq!(
6353            room.remote_participants()
6354                .get(&client_a.user_id().unwrap())
6355                .unwrap()
6356                .video_tracks
6357                .len(),
6358            1
6359        );
6360    });
6361}
6362
6363#[gpui::test]
6364async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
6365    let mut server = TestServer::start(cx.executor().clone()).await;
6366    let client_a = server.create_client(cx, "user_a").await;
6367    let (_workspace_a, cx) = client_a.build_test_workspace(cx).await;
6368
6369    cx.simulate_resize(size(px(300.), px(300.)));
6370
6371    cx.simulate_keystrokes("cmd-n cmd-n cmd-n");
6372    cx.update(|window, _cx| window.refresh());
6373
6374    let tab_bounds = cx.debug_bounds("TAB-2").unwrap();
6375    let new_tab_button_bounds = cx.debug_bounds("ICON-Plus").unwrap();
6376
6377    assert!(
6378        tab_bounds.intersects(&new_tab_button_bounds),
6379        "Tab should overlap with the new tab button, if this is failing check if there's been a redesign!"
6380    );
6381
6382    cx.simulate_event(MouseDownEvent {
6383        button: MouseButton::Right,
6384        position: new_tab_button_bounds.center(),
6385        modifiers: Modifiers::default(),
6386        click_count: 1,
6387        first_mouse: false,
6388    });
6389
6390    // regression test that the right click menu for tabs does not open.
6391    assert!(cx.debug_bounds("MENU_ITEM-Close").is_none());
6392
6393    let tab_bounds = cx.debug_bounds("TAB-1").unwrap();
6394    cx.simulate_event(MouseDownEvent {
6395        button: MouseButton::Right,
6396        position: tab_bounds.center(),
6397        modifiers: Modifiers::default(),
6398        click_count: 1,
6399        first_mouse: false,
6400    });
6401    assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
6402}
6403
6404#[gpui::test]
6405async fn test_pane_split_left(cx: &mut TestAppContext) {
6406    let (_, client) = TestServer::start1(cx).await;
6407    let (workspace, cx) = client.build_test_workspace(cx).await;
6408
6409    cx.simulate_keystrokes("cmd-n");
6410    workspace.update(cx, |workspace, cx| {
6411        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
6412    });
6413    cx.simulate_keystrokes("cmd-k left");
6414    workspace.update(cx, |workspace, cx| {
6415        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6416    });
6417    cx.simulate_keystrokes("cmd-k");
6418    // sleep for longer than the timeout in keyboard shortcut handling
6419    // to verify that it doesn't fire in this case.
6420    cx.executor().advance_clock(Duration::from_secs(2));
6421    cx.simulate_keystrokes("left");
6422    workspace.update(cx, |workspace, cx| {
6423        assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
6424    });
6425}
6426
6427#[gpui::test]
6428async fn test_join_after_restart(cx1: &mut TestAppContext, cx2: &mut TestAppContext) {
6429    let (mut server, client) = TestServer::start1(cx1).await;
6430    let channel1 = server.make_public_channel("channel1", &client, cx1).await;
6431    let channel2 = server.make_public_channel("channel2", &client, cx1).await;
6432
6433    join_channel(channel1, &client, cx1).await.unwrap();
6434    drop(client);
6435
6436    let client2 = server.create_client(cx2, "user_a").await;
6437    join_channel(channel2, &client2, cx2).await.unwrap();
6438}
6439
6440#[gpui::test]
6441async fn test_preview_tabs(cx: &mut TestAppContext) {
6442    let (_server, client) = TestServer::start1(cx).await;
6443    let (workspace, cx) = client.build_test_workspace(cx).await;
6444    let project = workspace.read_with(cx, |workspace, _| workspace.project().clone());
6445
6446    let worktree_id = project.update(cx, |project, cx| {
6447        project.worktrees(cx).next().unwrap().read(cx).id()
6448    });
6449
6450    let path_1 = ProjectPath {
6451        worktree_id,
6452        path: Path::new("1.txt").into(),
6453    };
6454    let path_2 = ProjectPath {
6455        worktree_id,
6456        path: Path::new("2.js").into(),
6457    };
6458    let path_3 = ProjectPath {
6459        worktree_id,
6460        path: Path::new("3.rs").into(),
6461    };
6462
6463    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6464
6465    let get_path = |pane: &Pane, idx: usize, cx: &App| {
6466        pane.item_for_index(idx).unwrap().project_path(cx).unwrap()
6467    };
6468
6469    // Opening item 3 as a "permanent" tab
6470    workspace
6471        .update_in(cx, |workspace, window, cx| {
6472            workspace.open_path(path_3.clone(), None, false, window, cx)
6473        })
6474        .await
6475        .unwrap();
6476
6477    pane.update(cx, |pane, cx| {
6478        assert_eq!(pane.items_len(), 1);
6479        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6480        assert_eq!(pane.preview_item_id(), None);
6481
6482        assert!(!pane.can_navigate_backward());
6483        assert!(!pane.can_navigate_forward());
6484    });
6485
6486    // Open item 1 as preview
6487    workspace
6488        .update_in(cx, |workspace, window, cx| {
6489            workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
6490        })
6491        .await
6492        .unwrap();
6493
6494    pane.update(cx, |pane, cx| {
6495        assert_eq!(pane.items_len(), 2);
6496        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6497        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6498        assert_eq!(
6499            pane.preview_item_id(),
6500            Some(pane.items().nth(1).unwrap().item_id())
6501        );
6502
6503        assert!(pane.can_navigate_backward());
6504        assert!(!pane.can_navigate_forward());
6505    });
6506
6507    // Open item 2 as preview
6508    workspace
6509        .update_in(cx, |workspace, window, cx| {
6510            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6511        })
6512        .await
6513        .unwrap();
6514
6515    pane.update(cx, |pane, cx| {
6516        assert_eq!(pane.items_len(), 2);
6517        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6518        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6519        assert_eq!(
6520            pane.preview_item_id(),
6521            Some(pane.items().nth(1).unwrap().item_id())
6522        );
6523
6524        assert!(pane.can_navigate_backward());
6525        assert!(!pane.can_navigate_forward());
6526    });
6527
6528    // Going back should show item 1 as preview
6529    workspace
6530        .update_in(cx, |workspace, window, cx| {
6531            workspace.go_back(pane.downgrade(), window, cx)
6532        })
6533        .await
6534        .unwrap();
6535
6536    pane.update(cx, |pane, cx| {
6537        assert_eq!(pane.items_len(), 2);
6538        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6539        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6540        assert_eq!(
6541            pane.preview_item_id(),
6542            Some(pane.items().nth(1).unwrap().item_id())
6543        );
6544
6545        assert!(pane.can_navigate_backward());
6546        assert!(pane.can_navigate_forward());
6547    });
6548
6549    // Closing item 1
6550    pane.update_in(cx, |pane, window, cx| {
6551        pane.close_item_by_id(
6552            pane.active_item().unwrap().item_id(),
6553            workspace::SaveIntent::Skip,
6554            window,
6555            cx,
6556        )
6557    })
6558    .await
6559    .unwrap();
6560
6561    pane.update(cx, |pane, cx| {
6562        assert_eq!(pane.items_len(), 1);
6563        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6564        assert_eq!(pane.preview_item_id(), None);
6565
6566        assert!(pane.can_navigate_backward());
6567        assert!(!pane.can_navigate_forward());
6568    });
6569
6570    // Going back should show item 1 as preview
6571    workspace
6572        .update_in(cx, |workspace, window, cx| {
6573            workspace.go_back(pane.downgrade(), window, cx)
6574        })
6575        .await
6576        .unwrap();
6577
6578    pane.update(cx, |pane, cx| {
6579        assert_eq!(pane.items_len(), 2);
6580        assert_eq!(get_path(pane, 0, cx), path_3.clone());
6581        assert_eq!(get_path(pane, 1, cx), path_1.clone());
6582        assert_eq!(
6583            pane.preview_item_id(),
6584            Some(pane.items().nth(1).unwrap().item_id())
6585        );
6586
6587        assert!(pane.can_navigate_backward());
6588        assert!(pane.can_navigate_forward());
6589    });
6590
6591    // Close permanent tab
6592    pane.update_in(cx, |pane, window, cx| {
6593        let id = pane.items().next().unwrap().item_id();
6594        pane.close_item_by_id(id, workspace::SaveIntent::Skip, window, cx)
6595    })
6596    .await
6597    .unwrap();
6598
6599    pane.update(cx, |pane, cx| {
6600        assert_eq!(pane.items_len(), 1);
6601        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6602        assert_eq!(
6603            pane.preview_item_id(),
6604            Some(pane.items().next().unwrap().item_id())
6605        );
6606
6607        assert!(pane.can_navigate_backward());
6608        assert!(pane.can_navigate_forward());
6609    });
6610
6611    // Split pane to the right
6612    pane.update(cx, |pane, cx| {
6613        pane.split(workspace::SplitDirection::Right, cx);
6614    });
6615
6616    let right_pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
6617
6618    pane.update(cx, |pane, cx| {
6619        assert_eq!(pane.items_len(), 1);
6620        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6621        assert_eq!(
6622            pane.preview_item_id(),
6623            Some(pane.items().next().unwrap().item_id())
6624        );
6625
6626        assert!(pane.can_navigate_backward());
6627        assert!(pane.can_navigate_forward());
6628    });
6629
6630    right_pane.update(cx, |pane, cx| {
6631        assert_eq!(pane.items_len(), 1);
6632        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6633        assert_eq!(pane.preview_item_id(), None);
6634
6635        assert!(!pane.can_navigate_backward());
6636        assert!(!pane.can_navigate_forward());
6637    });
6638
6639    // Open item 2 as preview in right pane
6640    workspace
6641        .update_in(cx, |workspace, window, cx| {
6642            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6643        })
6644        .await
6645        .unwrap();
6646
6647    pane.update(cx, |pane, cx| {
6648        assert_eq!(pane.items_len(), 1);
6649        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6650        assert_eq!(
6651            pane.preview_item_id(),
6652            Some(pane.items().next().unwrap().item_id())
6653        );
6654
6655        assert!(pane.can_navigate_backward());
6656        assert!(pane.can_navigate_forward());
6657    });
6658
6659    right_pane.update(cx, |pane, cx| {
6660        assert_eq!(pane.items_len(), 2);
6661        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6662        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6663        assert_eq!(
6664            pane.preview_item_id(),
6665            Some(pane.items().nth(1).unwrap().item_id())
6666        );
6667
6668        assert!(pane.can_navigate_backward());
6669        assert!(!pane.can_navigate_forward());
6670    });
6671
6672    // Focus left pane
6673    workspace.update_in(cx, |workspace, window, cx| {
6674        workspace.activate_pane_in_direction(workspace::SplitDirection::Left, window, cx)
6675    });
6676
6677    // Open item 2 as preview in left pane
6678    workspace
6679        .update_in(cx, |workspace, window, cx| {
6680            workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
6681        })
6682        .await
6683        .unwrap();
6684
6685    pane.update(cx, |pane, cx| {
6686        assert_eq!(pane.items_len(), 1);
6687        assert_eq!(get_path(pane, 0, cx), path_2.clone());
6688        assert_eq!(
6689            pane.preview_item_id(),
6690            Some(pane.items().next().unwrap().item_id())
6691        );
6692
6693        assert!(pane.can_navigate_backward());
6694        assert!(!pane.can_navigate_forward());
6695    });
6696
6697    right_pane.update(cx, |pane, cx| {
6698        assert_eq!(pane.items_len(), 2);
6699        assert_eq!(get_path(pane, 0, cx), path_1.clone());
6700        assert_eq!(get_path(pane, 1, cx), path_2.clone());
6701        assert_eq!(
6702            pane.preview_item_id(),
6703            Some(pane.items().nth(1).unwrap().item_id())
6704        );
6705
6706        assert!(pane.can_navigate_backward());
6707        assert!(!pane.can_navigate_forward());
6708    });
6709}
6710
6711#[gpui::test(iterations = 10)]
6712async fn test_context_collaboration_with_reconnect(
6713    executor: BackgroundExecutor,
6714    cx_a: &mut TestAppContext,
6715    cx_b: &mut TestAppContext,
6716) {
6717    let mut server = TestServer::start(executor.clone()).await;
6718    let client_a = server.create_client(cx_a, "user_a").await;
6719    let client_b = server.create_client(cx_b, "user_b").await;
6720    server
6721        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6722        .await;
6723    let active_call_a = cx_a.read(ActiveCall::global);
6724
6725    client_a.fs().insert_tree("/a", Default::default()).await;
6726    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
6727    let project_id = active_call_a
6728        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6729        .await
6730        .unwrap();
6731    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6732
6733    // Client A sees that a guest has joined.
6734    executor.run_until_parked();
6735
6736    project_a.read_with(cx_a, |project, _| {
6737        assert_eq!(project.collaborators().len(), 1);
6738    });
6739    project_b.read_with(cx_b, |project, _| {
6740        assert_eq!(project.collaborators().len(), 1);
6741    });
6742
6743    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
6744    let context_store_a = cx_a
6745        .update(|cx| {
6746            ContextStore::new(
6747                project_a.clone(),
6748                prompt_builder.clone(),
6749                Arc::new(SlashCommandWorkingSet::default()),
6750                cx,
6751            )
6752        })
6753        .await
6754        .unwrap();
6755    let context_store_b = cx_b
6756        .update(|cx| {
6757            ContextStore::new(
6758                project_b.clone(),
6759                prompt_builder.clone(),
6760                Arc::new(SlashCommandWorkingSet::default()),
6761                cx,
6762            )
6763        })
6764        .await
6765        .unwrap();
6766
6767    // Client A creates a new chats.
6768    let context_a = context_store_a.update(cx_a, |store, cx| store.create(cx));
6769    executor.run_until_parked();
6770
6771    // Client B retrieves host's contexts and joins one.
6772    let context_b = context_store_b
6773        .update(cx_b, |store, cx| {
6774            let host_contexts = store.host_contexts().to_vec();
6775            assert_eq!(host_contexts.len(), 1);
6776            store.open_remote_context(host_contexts[0].id.clone(), cx)
6777        })
6778        .await
6779        .unwrap();
6780
6781    // Host and guest make changes
6782    context_a.update(cx_a, |context, cx| {
6783        context.buffer().update(cx, |buffer, cx| {
6784            buffer.edit([(0..0, "Host change\n")], None, cx)
6785        })
6786    });
6787    context_b.update(cx_b, |context, cx| {
6788        context.buffer().update(cx, |buffer, cx| {
6789            buffer.edit([(0..0, "Guest change\n")], None, cx)
6790        })
6791    });
6792    executor.run_until_parked();
6793    assert_eq!(
6794        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6795        "Guest change\nHost change\n"
6796    );
6797    assert_eq!(
6798        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6799        "Guest change\nHost change\n"
6800    );
6801
6802    // Disconnect client A and make some changes while disconnected.
6803    server.disconnect_client(client_a.peer_id().unwrap());
6804    server.forbid_connections();
6805    context_a.update(cx_a, |context, cx| {
6806        context.buffer().update(cx, |buffer, cx| {
6807            buffer.edit([(0..0, "Host offline change\n")], None, cx)
6808        })
6809    });
6810    context_b.update(cx_b, |context, cx| {
6811        context.buffer().update(cx, |buffer, cx| {
6812            buffer.edit([(0..0, "Guest offline change\n")], None, cx)
6813        })
6814    });
6815    executor.run_until_parked();
6816    assert_eq!(
6817        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6818        "Host offline change\nGuest change\nHost change\n"
6819    );
6820    assert_eq!(
6821        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6822        "Guest offline change\nGuest change\nHost change\n"
6823    );
6824
6825    // Allow client A to reconnect and verify that contexts converge.
6826    server.allow_connections();
6827    executor.advance_clock(RECEIVE_TIMEOUT);
6828    assert_eq!(
6829        context_a.read_with(cx_a, |context, cx| context.buffer().read(cx).text()),
6830        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6831    );
6832    assert_eq!(
6833        context_b.read_with(cx_b, |context, cx| context.buffer().read(cx).text()),
6834        "Guest offline change\nHost offline change\nGuest change\nHost change\n"
6835    );
6836
6837    // Client A disconnects without being able to reconnect. Context B becomes readonly.
6838    server.forbid_connections();
6839    server.disconnect_client(client_a.peer_id().unwrap());
6840    executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
6841    context_b.read_with(cx_b, |context, cx| {
6842        assert!(context.buffer().read(cx).read_only());
6843    });
6844}
6845
6846#[gpui::test]
6847async fn test_remote_git_branches(
6848    executor: BackgroundExecutor,
6849    cx_a: &mut TestAppContext,
6850    cx_b: &mut TestAppContext,
6851) {
6852    let mut server = TestServer::start(executor.clone()).await;
6853    let client_a = server.create_client(cx_a, "user_a").await;
6854    let client_b = server.create_client(cx_b, "user_b").await;
6855    server
6856        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
6857        .await;
6858    let active_call_a = cx_a.read(ActiveCall::global);
6859
6860    client_a
6861        .fs()
6862        .insert_tree("/project", serde_json::json!({ ".git":{} }))
6863        .await;
6864    let branches = ["main", "dev", "feature-1"];
6865    client_a
6866        .fs()
6867        .insert_branches(Path::new("/project/.git"), &branches);
6868    let branches_set = branches
6869        .into_iter()
6870        .map(ToString::to_string)
6871        .collect::<HashSet<_>>();
6872
6873    let (project_a, _) = client_a.build_local_project("/project", cx_a).await;
6874
6875    let project_id = active_call_a
6876        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
6877        .await
6878        .unwrap();
6879    let project_b = client_b.join_remote_project(project_id, cx_b).await;
6880
6881    // Client A sees that a guest has joined and the repo has been populated
6882    executor.run_until_parked();
6883
6884    let repo_b = cx_b.update(|cx| project_b.read(cx).active_repository(cx).unwrap());
6885
6886    let branches_b = cx_b
6887        .update(|cx| repo_b.update(cx, |repository, _| repository.branches()))
6888        .await
6889        .unwrap()
6890        .unwrap();
6891
6892    let new_branch = branches[2];
6893
6894    let branches_b = branches_b
6895        .into_iter()
6896        .map(|branch| branch.name().to_string())
6897        .collect::<HashSet<_>>();
6898
6899    assert_eq!(branches_b, branches_set);
6900
6901    cx_b.update(|cx| {
6902        repo_b.update(cx, |repository, _cx| {
6903            repository.change_branch(new_branch.to_string())
6904        })
6905    })
6906    .await
6907    .unwrap()
6908    .unwrap();
6909
6910    executor.run_until_parked();
6911
6912    let host_branch = cx_a.update(|cx| {
6913        project_a.update(cx, |project, cx| {
6914            project
6915                .repositories(cx)
6916                .values()
6917                .next()
6918                .unwrap()
6919                .read(cx)
6920                .branch
6921                .as_ref()
6922                .unwrap()
6923                .clone()
6924        })
6925    });
6926
6927    assert_eq!(host_branch.name(), branches[2]);
6928
6929    // Also try creating a new branch
6930    cx_b.update(|cx| {
6931        repo_b.update(cx, |repository, _cx| {
6932            repository.create_branch("totally-new-branch".to_string())
6933        })
6934    })
6935    .await
6936    .unwrap()
6937    .unwrap();
6938
6939    cx_b.update(|cx| {
6940        repo_b.update(cx, |repository, _cx| {
6941            repository.change_branch("totally-new-branch".to_string())
6942        })
6943    })
6944    .await
6945    .unwrap()
6946    .unwrap();
6947
6948    executor.run_until_parked();
6949
6950    let host_branch = cx_a.update(|cx| {
6951        project_a.update(cx, |project, cx| {
6952            project
6953                .repositories(cx)
6954                .values()
6955                .next()
6956                .unwrap()
6957                .read(cx)
6958                .branch
6959                .as_ref()
6960                .unwrap()
6961                .clone()
6962        })
6963    });
6964
6965    assert_eq!(host_branch.name(), "totally-new-branch");
6966}