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