remote_editing_tests.rs

   1/// todo(windows)
   2/// The tests in this file assume that server_cx is running on Windows too.
   3/// We neead to find a way to test Windows-Non-Windows interactions.
   4use crate::headless_project::HeadlessProject;
   5use agent::{AgentTool, ReadFileTool, ReadFileToolInput, Templates, Thread, ToolCallEventStream};
   6use client::{Client, UserStore};
   7use clock::FakeSystemClock;
   8use collections::{HashMap, HashSet};
   9use language_model::{LanguageModelToolResultContent, fake_provider::FakeLanguageModel};
  10use languages::rust_lang;
  11use prompt_store::ProjectContext;
  12
  13use extension::ExtensionHostProxy;
  14use fs::{FakeFs, Fs};
  15use gpui::{AppContext as _, Entity, SharedString, TestAppContext};
  16use http_client::{BlockedHttpClient, FakeHttpClient};
  17use language::{
  18    Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,
  19    language_settings::{AllLanguageSettings, LanguageSettings},
  20};
  21use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};
  22use node_runtime::NodeRuntime;
  23use project::{
  24    ProgressToken, Project,
  25    agent_server_store::AgentServerCommand,
  26    search::{SearchQuery, SearchResult},
  27};
  28use remote::RemoteClient;
  29use serde_json::json;
  30use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};
  31use smol::stream::StreamExt;
  32use std::{
  33    path::{Path, PathBuf},
  34    sync::Arc,
  35};
  36use unindent::Unindent as _;
  37use util::{path, paths::PathMatcher, rel_path::rel_path};
  38
  39#[gpui::test]
  40async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
  41    let fs = FakeFs::new(server_cx.executor());
  42    fs.insert_tree(
  43        path!("/code"),
  44        json!({
  45            "project1": {
  46                ".git": {},
  47                "README.md": "# project 1",
  48                "src": {
  49                    "lib.rs": "fn one() -> usize { 1 }"
  50                }
  51            },
  52            "project2": {
  53                "README.md": "# project 2",
  54            },
  55        }),
  56    )
  57    .await;
  58    fs.set_index_for_repo(
  59        Path::new(path!("/code/project1/.git")),
  60        &[("src/lib.rs", "fn one() -> usize { 0 }".into())],
  61    );
  62
  63    let (project, _headless) = init_test(&fs, cx, server_cx).await;
  64    let (worktree, _) = project
  65        .update(cx, |project, cx| {
  66            project.find_or_create_worktree(path!("/code/project1"), true, cx)
  67        })
  68        .await
  69        .unwrap();
  70
  71    // The client sees the worktree's contents.
  72    cx.executor().run_until_parked();
  73    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
  74    worktree.update(cx, |worktree, _cx| {
  75        assert_eq!(
  76            worktree.paths().collect::<Vec<_>>(),
  77            vec![
  78                rel_path("README.md"),
  79                rel_path("src"),
  80                rel_path("src/lib.rs"),
  81            ]
  82        );
  83    });
  84
  85    // The user opens a buffer in the remote worktree. The buffer's
  86    // contents are loaded from the remote filesystem.
  87    let buffer = project
  88        .update(cx, |project, cx| {
  89            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
  90        })
  91        .await
  92        .unwrap();
  93    let diff = project
  94        .update(cx, |project, cx| {
  95            project.open_unstaged_diff(buffer.clone(), cx)
  96        })
  97        .await
  98        .unwrap();
  99
 100    diff.update(cx, |diff, cx| {
 101        assert_eq!(
 102            diff.base_text_string(cx).unwrap(),
 103            "fn one() -> usize { 0 }"
 104        );
 105    });
 106
 107    buffer.update(cx, |buffer, cx| {
 108        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
 109        let ix = buffer.text().find('1').unwrap();
 110        buffer.edit([(ix..ix + 1, "100")], None, cx);
 111    });
 112
 113    // The user saves the buffer. The new contents are written to the
 114    // remote filesystem.
 115    project
 116        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
 117        .await
 118        .unwrap();
 119    assert_eq!(
 120        fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
 121        "fn one() -> usize { 100 }"
 122    );
 123
 124    // A new file is created in the remote filesystem. The user
 125    // sees the new file.
 126    fs.save(
 127        path!("/code/project1/src/main.rs").as_ref(),
 128        &"fn main() {}".into(),
 129        Default::default(),
 130    )
 131    .await
 132    .unwrap();
 133    cx.executor().run_until_parked();
 134    worktree.update(cx, |worktree, _cx| {
 135        assert_eq!(
 136            worktree.paths().collect::<Vec<_>>(),
 137            vec![
 138                rel_path("README.md"),
 139                rel_path("src"),
 140                rel_path("src/lib.rs"),
 141                rel_path("src/main.rs"),
 142            ]
 143        );
 144    });
 145
 146    // A file that is currently open in a buffer is renamed.
 147    fs.rename(
 148        path!("/code/project1/src/lib.rs").as_ref(),
 149        path!("/code/project1/src/lib2.rs").as_ref(),
 150        Default::default(),
 151    )
 152    .await
 153    .unwrap();
 154    cx.executor().run_until_parked();
 155    buffer.update(cx, |buffer, _| {
 156        assert_eq!(&**buffer.file().unwrap().path(), rel_path("src/lib2.rs"));
 157    });
 158
 159    fs.set_index_for_repo(
 160        Path::new(path!("/code/project1/.git")),
 161        &[("src/lib2.rs", "fn one() -> usize { 100 }".into())],
 162    );
 163    cx.executor().run_until_parked();
 164    diff.update(cx, |diff, cx| {
 165        assert_eq!(
 166            diff.base_text_string(cx).unwrap(),
 167            "fn one() -> usize { 100 }"
 168        );
 169    });
 170}
 171
 172async fn do_search_and_assert(
 173    project: &Entity<Project>,
 174    query: &str,
 175    files_to_include: PathMatcher,
 176    match_full_paths: bool,
 177    expected_paths: &[&str],
 178    mut cx: TestAppContext,
 179) -> Vec<Entity<Buffer>> {
 180    let query = query.to_string();
 181    let receiver = project.update(&mut cx, |project, cx| {
 182        project.search(
 183            SearchQuery::text(
 184                query,
 185                false,
 186                true,
 187                false,
 188                files_to_include,
 189                Default::default(),
 190                match_full_paths,
 191                None,
 192            )
 193            .unwrap(),
 194            cx,
 195        )
 196    });
 197
 198    let mut buffers = Vec::new();
 199    for expected_path in expected_paths {
 200        let response = receiver.rx.recv().await.unwrap();
 201        let SearchResult::Buffer { buffer, .. } = response else {
 202            panic!("incorrect result");
 203        };
 204        buffer.update(&mut cx, |buffer, cx| {
 205            assert_eq!(
 206                buffer.file().unwrap().full_path(cx).to_string_lossy(),
 207                *expected_path
 208            )
 209        });
 210        buffers.push(buffer);
 211    }
 212
 213    assert!(receiver.rx.recv().await.is_err());
 214    buffers
 215}
 216
 217#[gpui::test]
 218async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 219    let fs = FakeFs::new(server_cx.executor());
 220    fs.insert_tree(
 221        path!("/code"),
 222        json!({
 223            "project1": {
 224                ".git": {},
 225                "README.md": "# project 1",
 226                "src": {
 227                    "lib.rs": "fn one() -> usize { 1 }"
 228                }
 229            },
 230        }),
 231    )
 232    .await;
 233
 234    let (project, headless) = init_test(&fs, cx, server_cx).await;
 235
 236    project
 237        .update(cx, |project, cx| {
 238            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 239        })
 240        .await
 241        .unwrap();
 242
 243    cx.run_until_parked();
 244
 245    let buffers = do_search_and_assert(
 246        &project,
 247        "project",
 248        Default::default(),
 249        false,
 250        &[path!("project1/README.md")],
 251        cx.clone(),
 252    )
 253    .await;
 254    let buffer = buffers.into_iter().next().unwrap();
 255
 256    // test that the headless server is tracking which buffers we have open correctly.
 257    cx.run_until_parked();
 258    headless.update(server_cx, |headless, cx| {
 259        assert!(headless.buffer_store.read(cx).has_shared_buffers())
 260    });
 261    do_search_and_assert(
 262        &project,
 263        "project",
 264        Default::default(),
 265        false,
 266        &[path!("project1/README.md")],
 267        cx.clone(),
 268    )
 269    .await;
 270    server_cx.run_until_parked();
 271    cx.update(|_| {
 272        drop(buffer);
 273    });
 274    cx.run_until_parked();
 275    server_cx.run_until_parked();
 276    headless.update(server_cx, |headless, cx| {
 277        assert!(!headless.buffer_store.read(cx).has_shared_buffers())
 278    });
 279
 280    do_search_and_assert(
 281        &project,
 282        "project",
 283        Default::default(),
 284        false,
 285        &[path!("project1/README.md")],
 286        cx.clone(),
 287    )
 288    .await;
 289}
 290
 291#[gpui::test]
 292async fn test_remote_project_search_inclusion(
 293    cx: &mut TestAppContext,
 294    server_cx: &mut TestAppContext,
 295) {
 296    let fs = FakeFs::new(server_cx.executor());
 297    fs.insert_tree(
 298        path!("/code"),
 299        json!({
 300            "project1": {
 301                "README.md": "# project 1",
 302            },
 303            "project2": {
 304                "README.md": "# project 2",
 305            },
 306        }),
 307    )
 308    .await;
 309
 310    let (project, _) = init_test(&fs, cx, server_cx).await;
 311
 312    project
 313        .update(cx, |project, cx| {
 314            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 315        })
 316        .await
 317        .unwrap();
 318
 319    project
 320        .update(cx, |project, cx| {
 321            project.find_or_create_worktree(path!("/code/project2"), true, cx)
 322        })
 323        .await
 324        .unwrap();
 325
 326    cx.run_until_parked();
 327
 328    // Case 1: Test search with path matcher limiting to only one worktree
 329    let path_matcher = PathMatcher::new(
 330        &["project1/*.md".to_owned()],
 331        util::paths::PathStyle::local(),
 332    )
 333    .unwrap();
 334    do_search_and_assert(
 335        &project,
 336        "project",
 337        path_matcher,
 338        true, // should be true in case of multiple worktrees
 339        &[path!("project1/README.md")],
 340        cx.clone(),
 341    )
 342    .await;
 343
 344    // Case 2: Test search without path matcher, matching both worktrees
 345    do_search_and_assert(
 346        &project,
 347        "project",
 348        Default::default(),
 349        true, // should be true in case of multiple worktrees
 350        &[path!("project1/README.md"), path!("project2/README.md")],
 351        cx.clone(),
 352    )
 353    .await;
 354}
 355
 356#[gpui::test]
 357async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 358    let fs = FakeFs::new(server_cx.executor());
 359    fs.insert_tree(
 360        "/code",
 361        json!({
 362            "project1": {
 363                ".git": {},
 364                "README.md": "# project 1",
 365                "src": {
 366                    "lib.rs": "fn one() -> usize { 1 }"
 367                }
 368            },
 369        }),
 370    )
 371    .await;
 372
 373    let (project, headless) = init_test(&fs, cx, server_cx).await;
 374
 375    cx.update_global(|settings_store: &mut SettingsStore, cx| {
 376        settings_store.set_user_settings(
 377            r#"{"languages":{"Rust":{"language_servers":["from-local-settings"]}}}"#,
 378            cx,
 379        )
 380    })
 381    .unwrap();
 382
 383    cx.run_until_parked();
 384
 385    server_cx.read(|cx| {
 386        assert_eq!(
 387            AllLanguageSettings::get_global(cx)
 388                .language(None, Some(&"Rust".into()), cx)
 389                .language_servers,
 390            ["from-local-settings"],
 391            "User language settings should be synchronized with the server settings"
 392        )
 393    });
 394
 395    server_cx
 396        .update_global(|settings_store: &mut SettingsStore, cx| {
 397            settings_store.set_server_settings(
 398                r#"{"languages":{"Rust":{"language_servers":["from-server-settings"]}}}"#,
 399                cx,
 400            )
 401        })
 402        .unwrap();
 403
 404    cx.run_until_parked();
 405
 406    server_cx.read(|cx| {
 407        assert_eq!(
 408            AllLanguageSettings::get_global(cx)
 409                .language(None, Some(&"Rust".into()), cx)
 410                .language_servers,
 411            ["from-server-settings".to_string()],
 412            "Server language settings should take precedence over the user settings"
 413        )
 414    });
 415
 416    fs.insert_tree(
 417        "/code/project1/.zed",
 418        json!({
 419            "settings.json": r#"
 420                  {
 421                    "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}},
 422                    "lsp": {
 423                      "override-rust-analyzer": {
 424                        "binary": {
 425                          "path": "~/.cargo/bin/rust-analyzer"
 426                        }
 427                      }
 428                    }
 429                  }"#
 430        }),
 431    )
 432    .await;
 433
 434    let worktree_id = project
 435        .update(cx, |project, cx| {
 436            project.languages().add(rust_lang());
 437            project.find_or_create_worktree("/code/project1", true, cx)
 438        })
 439        .await
 440        .unwrap()
 441        .0
 442        .read_with(cx, |worktree, _| worktree.id());
 443
 444    let buffer = project
 445        .update(cx, |project, cx| {
 446            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
 447        })
 448        .await
 449        .unwrap();
 450    cx.run_until_parked();
 451
 452    server_cx.read(|cx| {
 453        let worktree_id = headless
 454            .read(cx)
 455            .worktree_store
 456            .read(cx)
 457            .worktrees()
 458            .next()
 459            .unwrap()
 460            .read(cx)
 461            .id();
 462        assert_eq!(
 463            AllLanguageSettings::get(
 464                Some(SettingsLocation {
 465                    worktree_id,
 466                    path: rel_path("src/lib.rs")
 467                }),
 468                cx
 469            )
 470            .language(None, Some(&"Rust".into()), cx)
 471            .language_servers,
 472            ["override-rust-analyzer".to_string()]
 473        )
 474    });
 475
 476    cx.read(|cx| {
 477        assert_eq!(
 478            LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers,
 479            ["override-rust-analyzer".to_string()]
 480        )
 481    });
 482}
 483
 484#[gpui::test]
 485async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 486    let fs = FakeFs::new(server_cx.executor());
 487    fs.insert_tree(
 488        path!("/code"),
 489        json!({
 490            "project1": {
 491                ".git": {},
 492                "README.md": "# project 1",
 493                "src": {
 494                    "lib.rs": "fn one() -> usize { 1 }"
 495                }
 496            },
 497        }),
 498    )
 499    .await;
 500
 501    let (project, headless) = init_test(&fs, cx, server_cx).await;
 502
 503    fs.insert_tree(
 504        path!("/code/project1/.zed"),
 505        json!({
 506            "settings.json": r#"
 507          {
 508            "languages": {"Rust":{"language_servers":["rust-analyzer", "fake-analyzer"]}},
 509            "lsp": {
 510              "rust-analyzer": {
 511                "binary": {
 512                  "path": "~/.cargo/bin/rust-analyzer"
 513                }
 514              },
 515              "fake-analyzer": {
 516               "binary": {
 517                "path": "~/.cargo/bin/rust-analyzer"
 518               }
 519              }
 520            }
 521          }"#
 522        }),
 523    )
 524    .await;
 525
 526    cx.update_entity(&project, |project, _| {
 527        project.languages().register_test_language(LanguageConfig {
 528            name: "Rust".into(),
 529            matcher: LanguageMatcher {
 530                path_suffixes: vec!["rs".into()],
 531                ..Default::default()
 532            },
 533            ..Default::default()
 534        });
 535        project.languages().register_fake_lsp_adapter(
 536            "Rust",
 537            FakeLspAdapter {
 538                name: "rust-analyzer",
 539                capabilities: lsp::ServerCapabilities {
 540                    completion_provider: Some(lsp::CompletionOptions::default()),
 541                    rename_provider: Some(lsp::OneOf::Left(true)),
 542                    ..lsp::ServerCapabilities::default()
 543                },
 544                ..FakeLspAdapter::default()
 545            },
 546        );
 547        project.languages().register_fake_lsp_adapter(
 548            "Rust",
 549            FakeLspAdapter {
 550                name: "fake-analyzer",
 551                capabilities: lsp::ServerCapabilities {
 552                    completion_provider: Some(lsp::CompletionOptions::default()),
 553                    rename_provider: Some(lsp::OneOf::Left(true)),
 554                    ..lsp::ServerCapabilities::default()
 555                },
 556                ..FakeLspAdapter::default()
 557            },
 558        )
 559    });
 560
 561    let mut fake_lsp = server_cx.update(|cx| {
 562        headless.read(cx).languages.register_fake_lsp_server(
 563            LanguageServerName("rust-analyzer".into()),
 564            lsp::ServerCapabilities {
 565                completion_provider: Some(lsp::CompletionOptions::default()),
 566                rename_provider: Some(lsp::OneOf::Left(true)),
 567                ..lsp::ServerCapabilities::default()
 568            },
 569            None,
 570        )
 571    });
 572
 573    let mut fake_second_lsp = server_cx.update(|cx| {
 574        headless.read(cx).languages.register_fake_lsp_adapter(
 575            "Rust",
 576            FakeLspAdapter {
 577                name: "fake-analyzer",
 578                capabilities: lsp::ServerCapabilities {
 579                    completion_provider: Some(lsp::CompletionOptions::default()),
 580                    rename_provider: Some(lsp::OneOf::Left(true)),
 581                    ..lsp::ServerCapabilities::default()
 582                },
 583                ..FakeLspAdapter::default()
 584            },
 585        );
 586        headless.read(cx).languages.register_fake_lsp_server(
 587            LanguageServerName("fake-analyzer".into()),
 588            lsp::ServerCapabilities {
 589                completion_provider: Some(lsp::CompletionOptions::default()),
 590                rename_provider: Some(lsp::OneOf::Left(true)),
 591                ..lsp::ServerCapabilities::default()
 592            },
 593            None,
 594        )
 595    });
 596
 597    cx.run_until_parked();
 598
 599    let worktree_id = project
 600        .update(cx, |project, cx| {
 601            project.languages().add(rust_lang());
 602            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 603        })
 604        .await
 605        .unwrap()
 606        .0
 607        .read_with(cx, |worktree, _| worktree.id());
 608
 609    // Wait for the settings to synchronize
 610    cx.run_until_parked();
 611
 612    let (buffer, _handle) = project
 613        .update(cx, |project, cx| {
 614            project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
 615        })
 616        .await
 617        .unwrap();
 618    cx.run_until_parked();
 619
 620    let fake_lsp = fake_lsp.next().await.unwrap();
 621    let fake_second_lsp = fake_second_lsp.next().await.unwrap();
 622
 623    cx.read(|cx| {
 624        assert_eq!(
 625            LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers,
 626            ["rust-analyzer".to_string(), "fake-analyzer".to_string()]
 627        )
 628    });
 629
 630    let buffer_id = cx.read(|cx| {
 631        let buffer = buffer.read(cx);
 632        assert_eq!(buffer.language().unwrap().name(), "Rust".into());
 633        buffer.remote_id()
 634    });
 635
 636    server_cx.read(|cx| {
 637        let buffer = headless
 638            .read(cx)
 639            .buffer_store
 640            .read(cx)
 641            .get(buffer_id)
 642            .unwrap();
 643
 644        assert_eq!(buffer.read(cx).language().unwrap().name(), "Rust".into());
 645    });
 646
 647    server_cx.read(|cx| {
 648        let lsp_store = headless.read(cx).lsp_store.read(cx);
 649        assert_eq!(lsp_store.as_local().unwrap().language_servers.len(), 2);
 650    });
 651
 652    fake_lsp.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move {
 653        Ok(Some(CompletionResponse::Array(vec![lsp::CompletionItem {
 654            label: "boop".to_string(),
 655            ..Default::default()
 656        }])))
 657    });
 658
 659    fake_second_lsp.set_request_handler::<lsp::request::Completion, _, _>(|_, _| async move {
 660        Ok(Some(CompletionResponse::Array(vec![lsp::CompletionItem {
 661            label: "beep".to_string(),
 662            ..Default::default()
 663        }])))
 664    });
 665
 666    let result = project
 667        .update(cx, |project, cx| {
 668            project.completions(
 669                &buffer,
 670                0,
 671                CompletionContext {
 672                    trigger_kind: CompletionTriggerKind::INVOKED,
 673                    trigger_character: None,
 674                },
 675                cx,
 676            )
 677        })
 678        .await
 679        .unwrap();
 680
 681    assert_eq!(
 682        result
 683            .into_iter()
 684            .flat_map(|response| response.completions)
 685            .map(|c| c.label.text)
 686            .collect::<Vec<_>>(),
 687        vec!["boop".to_string(), "beep".to_string()]
 688    );
 689
 690    fake_lsp.set_request_handler::<lsp::request::Rename, _, _>(|_, _| async move {
 691        Ok(Some(lsp::WorkspaceEdit {
 692            changes: Some(
 693                [(
 694                    lsp::Uri::from_file_path(path!("/code/project1/src/lib.rs")).unwrap(),
 695                    vec![lsp::TextEdit::new(
 696                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 6)),
 697                        "two".to_string(),
 698                    )],
 699                )]
 700                .into_iter()
 701                .collect(),
 702            ),
 703            ..Default::default()
 704        }))
 705    });
 706
 707    project
 708        .update(cx, |project, cx| {
 709            project.perform_rename(buffer.clone(), 3, "two".to_string(), cx)
 710        })
 711        .await
 712        .unwrap();
 713
 714    cx.run_until_parked();
 715    buffer.update(cx, |buffer, _| {
 716        assert_eq!(buffer.text(), "fn two() -> usize { 1 }")
 717    })
 718}
 719
 720#[gpui::test]
 721async fn test_remote_cancel_language_server_work(
 722    cx: &mut TestAppContext,
 723    server_cx: &mut TestAppContext,
 724) {
 725    let fs = FakeFs::new(server_cx.executor());
 726    fs.insert_tree(
 727        path!("/code"),
 728        json!({
 729            "project1": {
 730                ".git": {},
 731                "README.md": "# project 1",
 732                "src": {
 733                    "lib.rs": "fn one() -> usize { 1 }"
 734                }
 735            },
 736        }),
 737    )
 738    .await;
 739
 740    let (project, headless) = init_test(&fs, cx, server_cx).await;
 741
 742    fs.insert_tree(
 743        path!("/code/project1/.zed"),
 744        json!({
 745            "settings.json": r#"
 746          {
 747            "languages": {"Rust":{"language_servers":["rust-analyzer"]}},
 748            "lsp": {
 749              "rust-analyzer": {
 750                "binary": {
 751                  "path": "~/.cargo/bin/rust-analyzer"
 752                }
 753              }
 754            }
 755          }"#
 756        }),
 757    )
 758    .await;
 759
 760    cx.update_entity(&project, |project, _| {
 761        project.languages().register_test_language(LanguageConfig {
 762            name: "Rust".into(),
 763            matcher: LanguageMatcher {
 764                path_suffixes: vec!["rs".into()],
 765                ..Default::default()
 766            },
 767            ..Default::default()
 768        });
 769        project.languages().register_fake_lsp_adapter(
 770            "Rust",
 771            FakeLspAdapter {
 772                name: "rust-analyzer",
 773                ..Default::default()
 774            },
 775        )
 776    });
 777
 778    let mut fake_lsp = server_cx.update(|cx| {
 779        headless.read(cx).languages.register_fake_lsp_server(
 780            LanguageServerName("rust-analyzer".into()),
 781            Default::default(),
 782            None,
 783        )
 784    });
 785
 786    cx.run_until_parked();
 787
 788    let worktree_id = project
 789        .update(cx, |project, cx| {
 790            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 791        })
 792        .await
 793        .unwrap()
 794        .0
 795        .read_with(cx, |worktree, _| worktree.id());
 796
 797    cx.run_until_parked();
 798
 799    let (buffer, _handle) = project
 800        .update(cx, |project, cx| {
 801            project.open_buffer_with_lsp((worktree_id, rel_path("src/lib.rs")), cx)
 802        })
 803        .await
 804        .unwrap();
 805
 806    cx.run_until_parked();
 807
 808    let mut fake_lsp = fake_lsp.next().await.unwrap();
 809
 810    // Cancelling all language server work for a given buffer
 811    {
 812        // Two operations, one cancellable and one not.
 813        fake_lsp
 814            .start_progress_with(
 815                "another-token",
 816                lsp::WorkDoneProgressBegin {
 817                    cancellable: Some(false),
 818                    ..Default::default()
 819                },
 820            )
 821            .await;
 822
 823        let progress_token = "the-progress-token";
 824        fake_lsp
 825            .start_progress_with(
 826                progress_token,
 827                lsp::WorkDoneProgressBegin {
 828                    cancellable: Some(true),
 829                    ..Default::default()
 830                },
 831            )
 832            .await;
 833
 834        cx.executor().run_until_parked();
 835
 836        project.update(cx, |project, cx| {
 837            project.cancel_language_server_work_for_buffers([buffer.clone()], cx)
 838        });
 839
 840        cx.executor().run_until_parked();
 841
 842        // Verify the cancellation was received on the server side
 843        let cancel_notification = fake_lsp
 844            .receive_notification::<lsp::notification::WorkDoneProgressCancel>()
 845            .await;
 846        assert_eq!(
 847            cancel_notification.token,
 848            lsp::NumberOrString::String(progress_token.into())
 849        );
 850    }
 851
 852    // Cancelling work by server_id and token
 853    {
 854        let server_id = fake_lsp.server.server_id();
 855        let progress_token = "the-progress-token";
 856
 857        fake_lsp
 858            .start_progress_with(
 859                progress_token,
 860                lsp::WorkDoneProgressBegin {
 861                    cancellable: Some(true),
 862                    ..Default::default()
 863                },
 864            )
 865            .await;
 866
 867        cx.executor().run_until_parked();
 868
 869        project.update(cx, |project, cx| {
 870            project.cancel_language_server_work(
 871                server_id,
 872                Some(ProgressToken::String(SharedString::from(progress_token))),
 873                cx,
 874            )
 875        });
 876
 877        cx.executor().run_until_parked();
 878
 879        // Verify the cancellation was received on the server side
 880        let cancel_notification = fake_lsp
 881            .receive_notification::<lsp::notification::WorkDoneProgressCancel>()
 882            .await;
 883        assert_eq!(
 884            cancel_notification.token,
 885            lsp::NumberOrString::String(progress_token.to_owned())
 886        );
 887    }
 888}
 889
 890#[gpui::test]
 891async fn test_remote_reload(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 892    let fs = FakeFs::new(server_cx.executor());
 893    fs.insert_tree(
 894        path!("/code"),
 895        json!({
 896            "project1": {
 897                ".git": {},
 898                "README.md": "# project 1",
 899                "src": {
 900                    "lib.rs": "fn one() -> usize { 1 }"
 901                }
 902            },
 903        }),
 904    )
 905    .await;
 906
 907    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 908    let (worktree, _) = project
 909        .update(cx, |project, cx| {
 910            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 911        })
 912        .await
 913        .unwrap();
 914
 915    let worktree_id = cx.update(|cx| worktree.read(cx).id());
 916
 917    let buffer = project
 918        .update(cx, |project, cx| {
 919            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
 920        })
 921        .await
 922        .unwrap();
 923
 924    fs.save(
 925        &PathBuf::from(path!("/code/project1/src/lib.rs")),
 926        &("bangles".to_string().into()),
 927        LineEnding::Unix,
 928    )
 929    .await
 930    .unwrap();
 931
 932    cx.run_until_parked();
 933
 934    buffer.update(cx, |buffer, cx| {
 935        assert_eq!(buffer.text(), "bangles");
 936        buffer.edit([(0..0, "a")], None, cx);
 937    });
 938
 939    fs.save(
 940        &PathBuf::from(path!("/code/project1/src/lib.rs")),
 941        &("bloop".to_string().into()),
 942        LineEnding::Unix,
 943    )
 944    .await
 945    .unwrap();
 946
 947    cx.run_until_parked();
 948    cx.update(|cx| {
 949        assert!(buffer.read(cx).has_conflict());
 950    });
 951
 952    project
 953        .update(cx, |project, cx| {
 954            project.reload_buffers([buffer.clone()].into_iter().collect(), false, cx)
 955        })
 956        .await
 957        .unwrap();
 958    cx.run_until_parked();
 959
 960    cx.update(|cx| {
 961        assert!(!buffer.read(cx).has_conflict());
 962    });
 963}
 964
 965#[gpui::test]
 966async fn test_remote_resolve_path_in_buffer(
 967    cx: &mut TestAppContext,
 968    server_cx: &mut TestAppContext,
 969) {
 970    let fs = FakeFs::new(server_cx.executor());
 971    // Even though we are not testing anything from project1, it is necessary to test if project2 is picking up correct worktree
 972    fs.insert_tree(
 973        path!("/code"),
 974        json!({
 975            "project1": {
 976                ".git": {},
 977                "README.md": "# project 1",
 978                "src": {
 979                    "lib.rs": "fn one() -> usize { 1 }"
 980                }
 981            },
 982            "project2": {
 983                ".git": {},
 984                "README.md": "# project 2",
 985                "src": {
 986                    "lib.rs": "fn two() -> usize { 2 }"
 987                }
 988            }
 989        }),
 990    )
 991    .await;
 992
 993    let (project, _headless) = init_test(&fs, cx, server_cx).await;
 994
 995    let _ = project
 996        .update(cx, |project, cx| {
 997            project.find_or_create_worktree(path!("/code/project1"), true, cx)
 998        })
 999        .await
1000        .unwrap();
1001
1002    let (worktree2, _) = project
1003        .update(cx, |project, cx| {
1004            project.find_or_create_worktree(path!("/code/project2"), true, cx)
1005        })
1006        .await
1007        .unwrap();
1008
1009    let worktree2_id = cx.update(|cx| worktree2.read(cx).id());
1010
1011    cx.run_until_parked();
1012
1013    let buffer2 = project
1014        .update(cx, |project, cx| {
1015            project.open_buffer((worktree2_id, rel_path("src/lib.rs")), cx)
1016        })
1017        .await
1018        .unwrap();
1019
1020    let path = project
1021        .update(cx, |project, cx| {
1022            project.resolve_path_in_buffer(path!("/code/project2/README.md"), &buffer2, cx)
1023        })
1024        .await
1025        .unwrap();
1026    assert!(path.is_file());
1027    assert_eq!(path.abs_path().unwrap(), path!("/code/project2/README.md"));
1028
1029    let path = project
1030        .update(cx, |project, cx| {
1031            project.resolve_path_in_buffer("../README.md", &buffer2, cx)
1032        })
1033        .await
1034        .unwrap();
1035    assert!(path.is_file());
1036    assert_eq!(
1037        path.project_path().unwrap().clone(),
1038        (worktree2_id, rel_path("README.md")).into()
1039    );
1040
1041    let path = project
1042        .update(cx, |project, cx| {
1043            project.resolve_path_in_buffer("../src", &buffer2, cx)
1044        })
1045        .await
1046        .unwrap();
1047    assert_eq!(
1048        path.project_path().unwrap().clone(),
1049        (worktree2_id, rel_path("src")).into()
1050    );
1051    assert!(path.is_dir());
1052}
1053
1054#[gpui::test]
1055async fn test_remote_resolve_abs_path(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1056    let fs = FakeFs::new(server_cx.executor());
1057    fs.insert_tree(
1058        path!("/code"),
1059        json!({
1060            "project1": {
1061                ".git": {},
1062                "README.md": "# project 1",
1063                "src": {
1064                    "lib.rs": "fn one() -> usize { 1 }"
1065                }
1066            },
1067        }),
1068    )
1069    .await;
1070
1071    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1072
1073    let path = project
1074        .update(cx, |project, cx| {
1075            project.resolve_abs_path(path!("/code/project1/README.md"), cx)
1076        })
1077        .await
1078        .unwrap();
1079
1080    assert!(path.is_file());
1081    assert_eq!(path.abs_path().unwrap(), path!("/code/project1/README.md"));
1082
1083    let path = project
1084        .update(cx, |project, cx| {
1085            project.resolve_abs_path(path!("/code/project1/src"), cx)
1086        })
1087        .await
1088        .unwrap();
1089
1090    assert!(path.is_dir());
1091    assert_eq!(path.abs_path().unwrap(), path!("/code/project1/src"));
1092
1093    let path = project
1094        .update(cx, |project, cx| {
1095            project.resolve_abs_path(path!("/code/project1/DOESNOTEXIST"), cx)
1096        })
1097        .await;
1098    assert!(path.is_none());
1099}
1100
1101#[gpui::test(iterations = 10)]
1102async fn test_canceling_buffer_opening(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1103    let fs = FakeFs::new(server_cx.executor());
1104    fs.insert_tree(
1105        "/code",
1106        json!({
1107            "project1": {
1108                ".git": {},
1109                "README.md": "# project 1",
1110                "src": {
1111                    "lib.rs": "fn one() -> usize { 1 }"
1112                }
1113            },
1114        }),
1115    )
1116    .await;
1117
1118    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1119    let (worktree, _) = project
1120        .update(cx, |project, cx| {
1121            project.find_or_create_worktree("/code/project1", true, cx)
1122        })
1123        .await
1124        .unwrap();
1125    let worktree_id = worktree.read_with(cx, |tree, _| tree.id());
1126
1127    // Open a buffer on the client but cancel after a random amount of time.
1128    let buffer = project.update(cx, |p, cx| {
1129        p.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
1130    });
1131    cx.executor().simulate_random_delay().await;
1132    drop(buffer);
1133
1134    // Try opening the same buffer again as the client, and ensure we can
1135    // still do it despite the cancellation above.
1136    let buffer = project
1137        .update(cx, |p, cx| {
1138            p.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
1139        })
1140        .await
1141        .unwrap();
1142
1143    buffer.read_with(cx, |buf, _| {
1144        assert_eq!(buf.text(), "fn one() -> usize { 1 }")
1145    });
1146}
1147
1148#[gpui::test]
1149async fn test_adding_then_removing_then_adding_worktrees(
1150    cx: &mut TestAppContext,
1151    server_cx: &mut TestAppContext,
1152) {
1153    let fs = FakeFs::new(server_cx.executor());
1154    fs.insert_tree(
1155        path!("/code"),
1156        json!({
1157            "project1": {
1158                ".git": {},
1159                "README.md": "# project 1",
1160                "src": {
1161                    "lib.rs": "fn one() -> usize { 1 }"
1162                }
1163            },
1164            "project2": {
1165                "README.md": "# project 2",
1166            },
1167        }),
1168    )
1169    .await;
1170
1171    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1172    let (_worktree, _) = project
1173        .update(cx, |project, cx| {
1174            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1175        })
1176        .await
1177        .unwrap();
1178
1179    let (worktree_2, _) = project
1180        .update(cx, |project, cx| {
1181            project.find_or_create_worktree(path!("/code/project2"), true, cx)
1182        })
1183        .await
1184        .unwrap();
1185    let worktree_id_2 = worktree_2.read_with(cx, |tree, _| tree.id());
1186
1187    project.update(cx, |project, cx| project.remove_worktree(worktree_id_2, cx));
1188
1189    let (worktree_2, _) = project
1190        .update(cx, |project, cx| {
1191            project.find_or_create_worktree(path!("/code/project2"), true, cx)
1192        })
1193        .await
1194        .unwrap();
1195
1196    cx.run_until_parked();
1197    worktree_2.update(cx, |worktree, _cx| {
1198        assert!(worktree.is_visible());
1199        let entries = worktree.entries(true, 0).collect::<Vec<_>>();
1200        assert_eq!(entries.len(), 2);
1201        assert_eq!(entries[1].path.as_unix_str(), "README.md")
1202    })
1203}
1204
1205#[gpui::test]
1206async fn test_open_server_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1207    let fs = FakeFs::new(server_cx.executor());
1208    fs.insert_tree(
1209        path!("/code"),
1210        json!({
1211            "project1": {
1212                ".git": {},
1213                "README.md": "# project 1",
1214                "src": {
1215                    "lib.rs": "fn one() -> usize { 1 }"
1216                }
1217            },
1218        }),
1219    )
1220    .await;
1221
1222    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1223    let buffer = project.update(cx, |project, cx| project.open_server_settings(cx));
1224    cx.executor().run_until_parked();
1225
1226    let buffer = buffer.await.unwrap();
1227
1228    cx.update(|cx| {
1229        assert_eq!(
1230            buffer.read(cx).text(),
1231            initial_server_settings_content()
1232                .to_string()
1233                .replace("\r\n", "\n")
1234        )
1235    })
1236}
1237
1238#[gpui::test(iterations = 20)]
1239async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1240    let fs = FakeFs::new(server_cx.executor());
1241    fs.insert_tree(
1242        path!("/code"),
1243        json!({
1244            "project1": {
1245                ".git": {},
1246                "README.md": "# project 1",
1247                "src": {
1248                    "lib.rs": "fn one() -> usize { 1 }"
1249                }
1250            },
1251        }),
1252    )
1253    .await;
1254
1255    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1256
1257    let (worktree, _) = project
1258        .update(cx, |project, cx| {
1259            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1260        })
1261        .await
1262        .unwrap();
1263
1264    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
1265    let buffer = project
1266        .update(cx, |project, cx| {
1267            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
1268        })
1269        .await
1270        .unwrap();
1271
1272    buffer.update(cx, |buffer, cx| {
1273        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
1274        let ix = buffer.text().find('1').unwrap();
1275        buffer.edit([(ix..ix + 1, "100")], None, cx);
1276    });
1277
1278    let client = cx.read(|cx| project.read(cx).remote_client().unwrap());
1279    client
1280        .update(cx, |client, cx| client.simulate_disconnect(cx))
1281        .detach();
1282
1283    project
1284        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
1285        .await
1286        .unwrap();
1287
1288    assert_eq!(
1289        fs.load(path!("/code/project1/src/lib.rs").as_ref())
1290            .await
1291            .unwrap(),
1292        "fn one() -> usize { 100 }"
1293    );
1294}
1295
1296#[gpui::test]
1297async fn test_remote_root_rename(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1298    let fs = FakeFs::new(server_cx.executor());
1299    fs.insert_tree(
1300        "/code",
1301        json!({
1302            "project1": {
1303                ".git": {},
1304                "README.md": "# project 1",
1305            },
1306        }),
1307    )
1308    .await;
1309
1310    let (project, _) = init_test(&fs, cx, server_cx).await;
1311
1312    let (worktree, _) = project
1313        .update(cx, |project, cx| {
1314            project.find_or_create_worktree("/code/project1", true, cx)
1315        })
1316        .await
1317        .unwrap();
1318
1319    cx.run_until_parked();
1320
1321    fs.rename(
1322        &PathBuf::from("/code/project1"),
1323        &PathBuf::from("/code/project2"),
1324        Default::default(),
1325    )
1326    .await
1327    .unwrap();
1328
1329    cx.run_until_parked();
1330    worktree.update(cx, |worktree, _| {
1331        assert_eq!(worktree.root_name(), "project2")
1332    })
1333}
1334
1335#[gpui::test]
1336async fn test_remote_rename_entry(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1337    let fs = FakeFs::new(server_cx.executor());
1338    fs.insert_tree(
1339        "/code",
1340        json!({
1341            "project1": {
1342                ".git": {},
1343                "README.md": "# project 1",
1344            },
1345        }),
1346    )
1347    .await;
1348
1349    let (project, _) = init_test(&fs, cx, server_cx).await;
1350    let (worktree, _) = project
1351        .update(cx, |project, cx| {
1352            project.find_or_create_worktree("/code/project1", true, cx)
1353        })
1354        .await
1355        .unwrap();
1356
1357    cx.run_until_parked();
1358
1359    let entry = project
1360        .update(cx, |project, cx| {
1361            let worktree = worktree.read(cx);
1362            let entry = worktree.entry_for_path(rel_path("README.md")).unwrap();
1363            project.rename_entry(entry.id, (worktree.id(), rel_path("README.rst")).into(), cx)
1364        })
1365        .await
1366        .unwrap()
1367        .into_included()
1368        .unwrap();
1369
1370    cx.run_until_parked();
1371
1372    worktree.update(cx, |worktree, _| {
1373        assert_eq!(
1374            worktree.entry_for_path(rel_path("README.rst")).unwrap().id,
1375            entry.id
1376        )
1377    });
1378}
1379
1380#[gpui::test]
1381async fn test_copy_file_into_remote_project(
1382    cx: &mut TestAppContext,
1383    server_cx: &mut TestAppContext,
1384) {
1385    let remote_fs = FakeFs::new(server_cx.executor());
1386    remote_fs
1387        .insert_tree(
1388            path!("/code"),
1389            json!({
1390                "project1": {
1391                    ".git": {},
1392                    "README.md": "# project 1",
1393                    "src": {
1394                        "main.rs": ""
1395                    }
1396                },
1397            }),
1398        )
1399        .await;
1400
1401    let (project, _) = init_test(&remote_fs, cx, server_cx).await;
1402    let (worktree, _) = project
1403        .update(cx, |project, cx| {
1404            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1405        })
1406        .await
1407        .unwrap();
1408
1409    cx.run_until_parked();
1410
1411    let local_fs = project
1412        .read_with(cx, |project, _| project.fs().clone())
1413        .as_fake();
1414    local_fs
1415        .insert_tree(
1416            path!("/local-code"),
1417            json!({
1418                "dir1": {
1419                    "file1": "file 1 content",
1420                    "dir2": {
1421                        "file2": "file 2 content",
1422                        "dir3": {
1423                            "file3": ""
1424                        },
1425                        "dir4": {}
1426                    },
1427                    "dir5": {}
1428                },
1429                "file4": "file 4 content"
1430            }),
1431        )
1432        .await;
1433
1434    worktree
1435        .update(cx, |worktree, cx| {
1436            worktree.copy_external_entries(
1437                rel_path("src").into(),
1438                vec![
1439                    Path::new(path!("/local-code/dir1/file1")).into(),
1440                    Path::new(path!("/local-code/dir1/dir2")).into(),
1441                ],
1442                local_fs.clone(),
1443                cx,
1444            )
1445        })
1446        .await
1447        .unwrap();
1448
1449    assert_eq!(
1450        remote_fs.paths(true),
1451        vec![
1452            PathBuf::from(path!("/")),
1453            PathBuf::from(path!("/code")),
1454            PathBuf::from(path!("/code/project1")),
1455            PathBuf::from(path!("/code/project1/.git")),
1456            PathBuf::from(path!("/code/project1/README.md")),
1457            PathBuf::from(path!("/code/project1/src")),
1458            PathBuf::from(path!("/code/project1/src/dir2")),
1459            PathBuf::from(path!("/code/project1/src/file1")),
1460            PathBuf::from(path!("/code/project1/src/main.rs")),
1461            PathBuf::from(path!("/code/project1/src/dir2/dir3")),
1462            PathBuf::from(path!("/code/project1/src/dir2/dir4")),
1463            PathBuf::from(path!("/code/project1/src/dir2/file2")),
1464            PathBuf::from(path!("/code/project1/src/dir2/dir3/file3")),
1465        ]
1466    );
1467    assert_eq!(
1468        remote_fs
1469            .load(path!("/code/project1/src/file1").as_ref())
1470            .await
1471            .unwrap(),
1472        "file 1 content"
1473    );
1474    assert_eq!(
1475        remote_fs
1476            .load(path!("/code/project1/src/dir2/file2").as_ref())
1477            .await
1478            .unwrap(),
1479        "file 2 content"
1480    );
1481    assert_eq!(
1482        remote_fs
1483            .load(path!("/code/project1/src/dir2/dir3/file3").as_ref())
1484            .await
1485            .unwrap(),
1486        ""
1487    );
1488}
1489
1490#[gpui::test]
1491async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1492    let text_2 = "
1493        fn one() -> usize {
1494            1
1495        }
1496    "
1497    .unindent();
1498    let text_1 = "
1499        fn one() -> usize {
1500            0
1501        }
1502    "
1503    .unindent();
1504
1505    let fs = FakeFs::new(server_cx.executor());
1506    fs.insert_tree(
1507        "/code",
1508        json!({
1509            "project1": {
1510                ".git": {},
1511                "src": {
1512                    "lib.rs": text_2
1513                },
1514                "README.md": "# project 1",
1515            },
1516        }),
1517    )
1518    .await;
1519    fs.set_index_for_repo(
1520        Path::new("/code/project1/.git"),
1521        &[("src/lib.rs", text_1.clone())],
1522    );
1523    fs.set_head_for_repo(
1524        Path::new("/code/project1/.git"),
1525        &[("src/lib.rs", text_1.clone())],
1526        "deadbeef",
1527    );
1528
1529    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1530    let (worktree, _) = project
1531        .update(cx, |project, cx| {
1532            project.find_or_create_worktree("/code/project1", true, cx)
1533        })
1534        .await
1535        .unwrap();
1536    let worktree_id = cx.update(|cx| worktree.read(cx).id());
1537    cx.executor().run_until_parked();
1538
1539    let buffer = project
1540        .update(cx, |project, cx| {
1541            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
1542        })
1543        .await
1544        .unwrap();
1545    let diff = project
1546        .update(cx, |project, cx| {
1547            project.open_uncommitted_diff(buffer.clone(), cx)
1548        })
1549        .await
1550        .unwrap();
1551
1552    diff.read_with(cx, |diff, cx| {
1553        assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
1554        assert_eq!(
1555            diff.secondary_diff()
1556                .unwrap()
1557                .read(cx)
1558                .base_text_string(cx)
1559                .unwrap(),
1560            text_1
1561        );
1562    });
1563
1564    // stage the current buffer's contents
1565    fs.set_index_for_repo(
1566        Path::new("/code/project1/.git"),
1567        &[("src/lib.rs", text_2.clone())],
1568    );
1569
1570    cx.executor().run_until_parked();
1571    diff.read_with(cx, |diff, cx| {
1572        assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
1573        assert_eq!(
1574            diff.secondary_diff()
1575                .unwrap()
1576                .read(cx)
1577                .base_text_string(cx)
1578                .unwrap(),
1579            text_2
1580        );
1581    });
1582
1583    // commit the current buffer's contents
1584    fs.set_head_for_repo(
1585        Path::new("/code/project1/.git"),
1586        &[("src/lib.rs", text_2.clone())],
1587        "deadbeef",
1588    );
1589
1590    cx.executor().run_until_parked();
1591    diff.read_with(cx, |diff, cx| {
1592        assert_eq!(diff.base_text_string(cx).unwrap(), text_2);
1593        assert_eq!(
1594            diff.secondary_diff()
1595                .unwrap()
1596                .read(cx)
1597                .base_text_string(cx)
1598                .unwrap(),
1599            text_2
1600        );
1601    });
1602}
1603
1604#[gpui::test]
1605async fn test_remote_git_diffs_when_recv_update_repository_delay(
1606    cx: &mut TestAppContext,
1607    server_cx: &mut TestAppContext,
1608) {
1609    cx.update(|cx| {
1610        let settings_store = SettingsStore::test(cx);
1611        cx.set_global(settings_store);
1612        theme::init(theme::LoadThemes::JustBase, cx);
1613        release_channel::init(semver::Version::new(0, 0, 0), cx);
1614        editor::init(cx);
1615    });
1616
1617    use editor::Editor;
1618    use gpui::VisualContext;
1619    let text_2 = "
1620        fn one() -> usize {
1621            1
1622        }
1623    "
1624    .unindent();
1625    let text_1 = "
1626        fn one() -> usize {
1627            0
1628        }
1629    "
1630    .unindent();
1631
1632    let fs = FakeFs::new(server_cx.executor());
1633    fs.insert_tree(
1634        path!("/code"),
1635        json!({
1636            "project1": {
1637                "src": {
1638                    "lib.rs": text_2
1639                },
1640                "README.md": "# project 1",
1641            },
1642        }),
1643    )
1644    .await;
1645
1646    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1647    let (worktree, _) = project
1648        .update(cx, |project, cx| {
1649            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1650        })
1651        .await
1652        .unwrap();
1653    let worktree_id = cx.update(|cx| worktree.read(cx).id());
1654    let buffer = project
1655        .update(cx, |project, cx| {
1656            project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx)
1657        })
1658        .await
1659        .unwrap();
1660    let buffer_id = cx.update(|cx| buffer.read(cx).remote_id());
1661
1662    let cx = cx.add_empty_window();
1663    let editor = cx.new_window_entity(|window, cx| {
1664        Editor::for_buffer(buffer, Some(project.clone()), window, cx)
1665    });
1666
1667    // Remote server will send proto::UpdateRepository after the instance of Editor create.
1668    fs.insert_tree(
1669        path!("/code"),
1670        json!({
1671            "project1": {
1672                ".git": {},
1673            },
1674        }),
1675    )
1676    .await;
1677
1678    fs.set_index_for_repo(
1679        Path::new(path!("/code/project1/.git")),
1680        &[("src/lib.rs", text_1.clone())],
1681    );
1682    fs.set_head_for_repo(
1683        Path::new(path!("/code/project1/.git")),
1684        &[("src/lib.rs", text_1.clone())],
1685        "sha",
1686    );
1687
1688    cx.executor().run_until_parked();
1689    let diff = editor
1690        .read_with(cx, |editor, cx| {
1691            editor
1692                .buffer()
1693                .read_with(cx, |buffer, _| buffer.diff_for(buffer_id))
1694        })
1695        .unwrap();
1696
1697    diff.read_with(cx, |diff, cx| {
1698        assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
1699        assert_eq!(
1700            diff.secondary_diff()
1701                .unwrap()
1702                .read(cx)
1703                .base_text_string(cx)
1704                .unwrap(),
1705            text_1
1706        );
1707    });
1708
1709    // stage the current buffer's contents
1710    fs.set_index_for_repo(
1711        Path::new(path!("/code/project1/.git")),
1712        &[("src/lib.rs", text_2.clone())],
1713    );
1714
1715    cx.executor().run_until_parked();
1716    diff.read_with(cx, |diff, cx| {
1717        assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
1718        assert_eq!(
1719            diff.secondary_diff()
1720                .unwrap()
1721                .read(cx)
1722                .base_text_string(cx)
1723                .unwrap(),
1724            text_2
1725        );
1726    });
1727
1728    // commit the current buffer's contents
1729    fs.set_head_for_repo(
1730        Path::new(path!("/code/project1/.git")),
1731        &[("src/lib.rs", text_2.clone())],
1732        "sha",
1733    );
1734
1735    cx.executor().run_until_parked();
1736    diff.read_with(cx, |diff, cx| {
1737        assert_eq!(diff.base_text_string(cx).unwrap(), text_2);
1738        assert_eq!(
1739            diff.secondary_diff()
1740                .unwrap()
1741                .read(cx)
1742                .base_text_string(cx)
1743                .unwrap(),
1744            text_2
1745        );
1746    });
1747}
1748
1749#[gpui::test]
1750async fn test_remote_git_branches(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1751    let fs = FakeFs::new(server_cx.executor());
1752    fs.insert_tree(
1753        path!("/code"),
1754        json!({
1755            "project1": {
1756                ".git": {},
1757                "README.md": "# project 1",
1758            },
1759        }),
1760    )
1761    .await;
1762
1763    let (project, headless_project) = init_test(&fs, cx, server_cx).await;
1764    let branches = ["main", "dev", "feature-1"];
1765    let branches_set = branches
1766        .iter()
1767        .map(ToString::to_string)
1768        .collect::<HashSet<_>>();
1769    fs.insert_branches(Path::new(path!("/code/project1/.git")), &branches);
1770
1771    let (_worktree, _) = project
1772        .update(cx, |project, cx| {
1773            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1774        })
1775        .await
1776        .unwrap();
1777    // Give the worktree a bit of time to index the file system
1778    cx.run_until_parked();
1779
1780    let repository = project.update(cx, |project, cx| project.active_repository(cx).unwrap());
1781
1782    let remote_branches = repository
1783        .update(cx, |repository, _| repository.branches())
1784        .await
1785        .unwrap()
1786        .unwrap();
1787
1788    let new_branch = branches[2];
1789
1790    let remote_branches = remote_branches
1791        .into_iter()
1792        .map(|branch| branch.name().to_string())
1793        .collect::<HashSet<_>>();
1794
1795    assert_eq!(&remote_branches, &branches_set);
1796
1797    cx.update(|cx| {
1798        repository.update(cx, |repository, _cx| {
1799            repository.change_branch(new_branch.to_string())
1800        })
1801    })
1802    .await
1803    .unwrap()
1804    .unwrap();
1805
1806    cx.run_until_parked();
1807
1808    let server_branch = server_cx.update(|cx| {
1809        headless_project.update(cx, |headless_project, cx| {
1810            headless_project.git_store.update(cx, |git_store, cx| {
1811                git_store
1812                    .repositories()
1813                    .values()
1814                    .next()
1815                    .unwrap()
1816                    .read(cx)
1817                    .branch
1818                    .as_ref()
1819                    .unwrap()
1820                    .clone()
1821            })
1822        })
1823    });
1824
1825    assert_eq!(server_branch.name(), branches[2]);
1826
1827    // Also try creating a new branch
1828    cx.update(|cx| {
1829        repository.update(cx, |repo, _cx| {
1830            repo.create_branch("totally-new-branch".to_string(), None)
1831        })
1832    })
1833    .await
1834    .unwrap()
1835    .unwrap();
1836
1837    cx.update(|cx| {
1838        repository.update(cx, |repo, _cx| {
1839            repo.change_branch("totally-new-branch".to_string())
1840        })
1841    })
1842    .await
1843    .unwrap()
1844    .unwrap();
1845
1846    cx.run_until_parked();
1847
1848    let server_branch = server_cx.update(|cx| {
1849        headless_project.update(cx, |headless_project, cx| {
1850            headless_project.git_store.update(cx, |git_store, cx| {
1851                git_store
1852                    .repositories()
1853                    .values()
1854                    .next()
1855                    .unwrap()
1856                    .read(cx)
1857                    .branch
1858                    .as_ref()
1859                    .unwrap()
1860                    .clone()
1861            })
1862        })
1863    });
1864
1865    assert_eq!(server_branch.name(), "totally-new-branch");
1866}
1867
1868#[gpui::test]
1869async fn test_remote_agent_fs_tool_calls(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1870    let fs = FakeFs::new(server_cx.executor());
1871    fs.insert_tree(
1872        path!("/project"),
1873        json!({
1874            "a.txt": "A",
1875            "b.txt": "B",
1876        }),
1877    )
1878    .await;
1879
1880    let (project, _headless_project) = init_test(&fs, cx, server_cx).await;
1881    project
1882        .update(cx, |project, cx| {
1883            project.find_or_create_worktree(path!("/project"), true, cx)
1884        })
1885        .await
1886        .unwrap();
1887
1888    let action_log = cx.new(|_| action_log::ActionLog::new(project.clone()));
1889
1890    // Create a minimal thread for the ReadFileTool
1891    let context_server_registry =
1892        cx.new(|cx| agent::ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
1893    let model = Arc::new(FakeLanguageModel::default());
1894    let thread = cx.new(|cx| {
1895        Thread::new(
1896            project.clone(),
1897            cx.new(|_cx| ProjectContext::default()),
1898            context_server_registry,
1899            Templates::new(),
1900            Some(model),
1901            cx,
1902        )
1903    });
1904
1905    let input = ReadFileToolInput {
1906        path: "project/b.txt".into(),
1907        start_line: None,
1908        end_line: None,
1909    };
1910    let read_tool = Arc::new(ReadFileTool::new(thread.downgrade(), project, action_log));
1911    let (event_stream, _) = ToolCallEventStream::test();
1912
1913    let exists_result = cx.update(|cx| read_tool.clone().run(input, event_stream.clone(), cx));
1914    let output = exists_result.await.unwrap();
1915    assert_eq!(output, LanguageModelToolResultContent::Text("B".into()));
1916
1917    let input = ReadFileToolInput {
1918        path: "project/c.txt".into(),
1919        start_line: None,
1920        end_line: None,
1921    };
1922    let does_not_exist_result = cx.update(|cx| read_tool.run(input, event_stream, cx));
1923    does_not_exist_result.await.unwrap_err();
1924}
1925
1926#[gpui::test]
1927async fn test_remote_external_agent_server(
1928    cx: &mut TestAppContext,
1929    server_cx: &mut TestAppContext,
1930) {
1931    let fs = FakeFs::new(server_cx.executor());
1932    fs.insert_tree(path!("/project"), json!({})).await;
1933
1934    let (project, _headless_project) = init_test(&fs, cx, server_cx).await;
1935    project
1936        .update(cx, |project, cx| {
1937            project.find_or_create_worktree(path!("/project"), true, cx)
1938        })
1939        .await
1940        .unwrap();
1941    let names = project.update(cx, |project, cx| {
1942        project
1943            .agent_server_store()
1944            .read(cx)
1945            .external_agents()
1946            .map(|name| name.to_string())
1947            .collect::<Vec<_>>()
1948    });
1949    pretty_assertions::assert_eq!(names, ["codex", "gemini", "claude"]);
1950    server_cx.update_global::<SettingsStore, _>(|settings_store, cx| {
1951        settings_store
1952            .set_server_settings(
1953                &json!({
1954                    "agent_servers": {
1955                        "foo": {
1956                            "type": "custom",
1957                            "command": "foo-cli",
1958                            "args": ["--flag"],
1959                            "env": {
1960                                "VAR": "val"
1961                            }
1962                        }
1963                    }
1964                })
1965                .to_string(),
1966                cx,
1967            )
1968            .unwrap();
1969    });
1970    server_cx.run_until_parked();
1971    cx.run_until_parked();
1972    let names = project.update(cx, |project, cx| {
1973        project
1974            .agent_server_store()
1975            .read(cx)
1976            .external_agents()
1977            .map(|name| name.to_string())
1978            .collect::<Vec<_>>()
1979    });
1980    pretty_assertions::assert_eq!(names, ["gemini", "codex", "claude", "foo"]);
1981    let (command, root, login) = project
1982        .update(cx, |project, cx| {
1983            project.agent_server_store().update(cx, |store, cx| {
1984                store
1985                    .get_external_agent(&"foo".into())
1986                    .unwrap()
1987                    .get_command(
1988                        None,
1989                        HashMap::from_iter([("OTHER_VAR".into(), "other-val".into())]),
1990                        None,
1991                        None,
1992                        &mut cx.to_async(),
1993                    )
1994            })
1995        })
1996        .await
1997        .unwrap();
1998    assert_eq!(
1999        command,
2000        AgentServerCommand {
2001            path: "mock".into(),
2002            args: vec!["foo-cli".into(), "--flag".into()],
2003            env: Some(HashMap::from_iter([
2004                ("VAR".into(), "val".into()),
2005                ("OTHER_VAR".into(), "other-val".into())
2006            ]))
2007        }
2008    );
2009    assert_eq!(&PathBuf::from(root), paths::home_dir());
2010    assert!(login.is_none());
2011}
2012
2013pub async fn init_test(
2014    server_fs: &Arc<FakeFs>,
2015    cx: &mut TestAppContext,
2016    server_cx: &mut TestAppContext,
2017) -> (Entity<Project>, Entity<HeadlessProject>) {
2018    let server_fs = server_fs.clone();
2019    cx.update(|cx| {
2020        release_channel::init(semver::Version::new(0, 0, 0), cx);
2021    });
2022    server_cx.update(|cx| {
2023        release_channel::init(semver::Version::new(0, 0, 0), cx);
2024    });
2025    init_logger();
2026
2027    let (opts, ssh_server_client, _) = RemoteClient::fake_server(cx, server_cx);
2028    let http_client = Arc::new(BlockedHttpClient);
2029    let node_runtime = NodeRuntime::unavailable();
2030    let languages = Arc::new(LanguageRegistry::new(cx.executor()));
2031    let proxy = Arc::new(ExtensionHostProxy::new());
2032    server_cx.update(HeadlessProject::init);
2033    let headless = server_cx.new(|cx| {
2034        HeadlessProject::new(
2035            crate::HeadlessAppState {
2036                session: ssh_server_client,
2037                fs: server_fs.clone(),
2038                http_client,
2039                node_runtime,
2040                languages,
2041                extension_host_proxy: proxy,
2042            },
2043            false,
2044            cx,
2045        )
2046    });
2047
2048    let ssh = RemoteClient::connect_mock(opts, cx).await;
2049    let project = build_project(ssh, cx);
2050    project
2051        .update(cx, {
2052            let headless = headless.clone();
2053            |_, cx| cx.on_release(|_, _| drop(headless))
2054        })
2055        .detach();
2056    (project, headless)
2057}
2058
2059fn init_logger() {
2060    zlog::init_test();
2061}
2062
2063fn build_project(ssh: Entity<RemoteClient>, cx: &mut TestAppContext) -> Entity<Project> {
2064    cx.update(|cx| {
2065        if !cx.has_global::<SettingsStore>() {
2066            let settings_store = SettingsStore::test(cx);
2067            cx.set_global(settings_store);
2068        }
2069    });
2070
2071    let client = cx.update(|cx| {
2072        Client::new(
2073            Arc::new(FakeSystemClock::new()),
2074            FakeHttpClient::with_404_response(),
2075            cx,
2076        )
2077    });
2078
2079    let node = NodeRuntime::unavailable();
2080    let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
2081    let languages = Arc::new(LanguageRegistry::test(cx.executor()));
2082    let fs = FakeFs::new(cx.executor());
2083
2084    cx.update(|cx| {
2085        Project::init(&client, cx);
2086    });
2087
2088    cx.update(|cx| Project::remote(ssh, client, node, user_store, languages, fs, false, cx))
2089}