integration_tests.rs

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