integration_tests.rs

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