integration_tests.rs

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