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_settings::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_git_checkpoints(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
1922    let fs = FakeFs::new(server_cx.executor());
1923    fs.insert_tree(
1924        path!("/code"),
1925        json!({
1926            "project1": {
1927                ".git": {},
1928                "file.txt": "original content",
1929            },
1930        }),
1931    )
1932    .await;
1933
1934    let (project, _headless) = init_test(&fs, cx, server_cx).await;
1935
1936    let (_worktree, _) = project
1937        .update(cx, |project, cx| {
1938            project.find_or_create_worktree(path!("/code/project1"), true, cx)
1939        })
1940        .await
1941        .unwrap();
1942    cx.run_until_parked();
1943
1944    let repository = project.update(cx, |project, cx| project.active_repository(cx).unwrap());
1945
1946    // 1. Create a checkpoint of the original state
1947    let checkpoint_1 = repository
1948        .update(cx, |repository, _| repository.checkpoint())
1949        .await
1950        .unwrap()
1951        .unwrap();
1952
1953    // 2. Modify a file on the server-side fs
1954    fs.write(
1955        Path::new(path!("/code/project1/file.txt")),
1956        b"modified content",
1957    )
1958    .await
1959    .unwrap();
1960
1961    // 3. Create a second checkpoint with the modified state
1962    let checkpoint_2 = repository
1963        .update(cx, |repository, _| repository.checkpoint())
1964        .await
1965        .unwrap()
1966        .unwrap();
1967
1968    // 4. compare_checkpoints: same checkpoint with itself => equal
1969    let equal = repository
1970        .update(cx, |repository, _| {
1971            repository.compare_checkpoints(checkpoint_1.clone(), checkpoint_1.clone())
1972        })
1973        .await
1974        .unwrap()
1975        .unwrap();
1976    assert!(equal, "a checkpoint compared with itself should be equal");
1977
1978    // 5. compare_checkpoints: different states => not equal
1979    let equal = repository
1980        .update(cx, |repository, _| {
1981            repository.compare_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
1982        })
1983        .await
1984        .unwrap()
1985        .unwrap();
1986    assert!(
1987        !equal,
1988        "checkpoints of different states should not be equal"
1989    );
1990
1991    // 6. diff_checkpoints: same checkpoint => empty diff
1992    let diff = repository
1993        .update(cx, |repository, _| {
1994            repository.diff_checkpoints(checkpoint_1.clone(), checkpoint_1.clone())
1995        })
1996        .await
1997        .unwrap()
1998        .unwrap();
1999    assert!(
2000        diff.is_empty(),
2001        "diff of identical checkpoints should be empty"
2002    );
2003
2004    // 7. diff_checkpoints: different checkpoints => non-empty diff mentioning the changed file
2005    let diff = repository
2006        .update(cx, |repository, _| {
2007            repository.diff_checkpoints(checkpoint_1.clone(), checkpoint_2.clone())
2008        })
2009        .await
2010        .unwrap()
2011        .unwrap();
2012    assert!(
2013        !diff.is_empty(),
2014        "diff of different checkpoints should be non-empty"
2015    );
2016    assert!(
2017        diff.contains("file.txt"),
2018        "diff should mention the changed file"
2019    );
2020    assert!(
2021        diff.contains("original content"),
2022        "diff should contain removed content"
2023    );
2024    assert!(
2025        diff.contains("modified content"),
2026        "diff should contain added content"
2027    );
2028
2029    // 8. restore_checkpoint: restore to original state
2030    repository
2031        .update(cx, |repository, _| {
2032            repository.restore_checkpoint(checkpoint_1.clone())
2033        })
2034        .await
2035        .unwrap()
2036        .unwrap();
2037    cx.run_until_parked();
2038
2039    // 9. Create a checkpoint after restore
2040    let checkpoint_3 = repository
2041        .update(cx, |repository, _| repository.checkpoint())
2042        .await
2043        .unwrap()
2044        .unwrap();
2045
2046    // 10. compare_checkpoints: restored state matches original
2047    let equal = repository
2048        .update(cx, |repository, _| {
2049            repository.compare_checkpoints(checkpoint_1.clone(), checkpoint_3.clone())
2050        })
2051        .await
2052        .unwrap()
2053        .unwrap();
2054    assert!(equal, "restored state should match original checkpoint");
2055
2056    // 11. diff_checkpoints: restored state vs original => empty diff
2057    let diff = repository
2058        .update(cx, |repository, _| {
2059            repository.diff_checkpoints(checkpoint_1.clone(), checkpoint_3.clone())
2060        })
2061        .await
2062        .unwrap()
2063        .unwrap();
2064    assert!(diff.is_empty(), "diff after restore should be empty");
2065}
2066
2067#[gpui::test]
2068async fn test_remote_agent_fs_tool_calls(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
2069    let fs = FakeFs::new(server_cx.executor());
2070    fs.insert_tree(
2071        path!("/project"),
2072        json!({
2073            "a.txt": "A",
2074            "b.txt": "B",
2075        }),
2076    )
2077    .await;
2078
2079    let (project, _headless_project) = init_test(&fs, cx, server_cx).await;
2080    project
2081        .update(cx, |project, cx| {
2082            project.find_or_create_worktree(path!("/project"), true, cx)
2083        })
2084        .await
2085        .unwrap();
2086
2087    let action_log = cx.new(|_| action_log::ActionLog::new(project.clone()));
2088
2089    let input = ReadFileToolInput {
2090        path: "project/b.txt".into(),
2091        start_line: None,
2092        end_line: None,
2093    };
2094    let read_tool = Arc::new(ReadFileTool::new(project, action_log, true));
2095    let (event_stream, _) = ToolCallEventStream::test();
2096
2097    let exists_result = cx.update(|cx| {
2098        read_tool
2099            .clone()
2100            .run(ToolInput::resolved(input), event_stream.clone(), cx)
2101    });
2102    let output = exists_result.await.unwrap();
2103    assert_eq!(output, LanguageModelToolResultContent::Text("B".into()));
2104
2105    let input = ReadFileToolInput {
2106        path: "project/c.txt".into(),
2107        start_line: None,
2108        end_line: None,
2109    };
2110    let does_not_exist_result =
2111        cx.update(|cx| read_tool.run(ToolInput::resolved(input), event_stream, cx));
2112    does_not_exist_result.await.unwrap_err();
2113}
2114
2115#[gpui::test]
2116async fn test_remote_external_agent_server(
2117    cx: &mut TestAppContext,
2118    server_cx: &mut TestAppContext,
2119) {
2120    let fs = FakeFs::new(server_cx.executor());
2121    fs.insert_tree(path!("/project"), json!({})).await;
2122
2123    let (project, _headless_project) = init_test(&fs, cx, server_cx).await;
2124    project
2125        .update(cx, |project, cx| {
2126            project.find_or_create_worktree(path!("/project"), true, cx)
2127        })
2128        .await
2129        .unwrap();
2130    let names = project.update(cx, |project, cx| {
2131        project
2132            .agent_server_store()
2133            .read(cx)
2134            .external_agents()
2135            .map(|name| name.to_string())
2136            .collect::<Vec<_>>()
2137    });
2138    pretty_assertions::assert_eq!(names, Vec::<String>::new());
2139    server_cx.update_global::<SettingsStore, _>(|settings_store, cx| {
2140        settings_store
2141            .set_server_settings(
2142                &json!({
2143                    "agent_servers": {
2144                        "foo": {
2145                            "type": "custom",
2146                            "command": "foo-cli",
2147                            "args": ["--flag"],
2148                            "env": {
2149                                "VAR": "val"
2150                            }
2151                        }
2152                    }
2153                })
2154                .to_string(),
2155                cx,
2156            )
2157            .unwrap();
2158    });
2159    server_cx.run_until_parked();
2160    cx.run_until_parked();
2161    let names = project.update(cx, |project, cx| {
2162        project
2163            .agent_server_store()
2164            .read(cx)
2165            .external_agents()
2166            .map(|name| name.to_string())
2167            .collect::<Vec<_>>()
2168    });
2169    pretty_assertions::assert_eq!(names, ["foo"]);
2170    let command = project
2171        .update(cx, |project, cx| {
2172            project.agent_server_store().update(cx, |store, cx| {
2173                store
2174                    .get_external_agent(&"foo".into())
2175                    .unwrap()
2176                    .get_command(
2177                        HashMap::from_iter([("OTHER_VAR".into(), "other-val".into())]),
2178                        None,
2179                        &mut cx.to_async(),
2180                    )
2181            })
2182        })
2183        .await
2184        .unwrap();
2185    assert_eq!(
2186        command,
2187        AgentServerCommand {
2188            path: "mock".into(),
2189            args: vec!["foo-cli".into(), "--flag".into()],
2190            env: Some(HashMap::from_iter([
2191                ("NO_BROWSER".into(), "1".into()),
2192                ("VAR".into(), "val".into()),
2193                ("OTHER_VAR".into(), "other-val".into())
2194            ]))
2195        }
2196    );
2197}
2198
2199pub async fn init_test(
2200    server_fs: &Arc<FakeFs>,
2201    cx: &mut TestAppContext,
2202    server_cx: &mut TestAppContext,
2203) -> (Entity<Project>, Entity<HeadlessProject>) {
2204    let server_fs = server_fs.clone();
2205    cx.update(|cx| {
2206        release_channel::init(semver::Version::new(0, 0, 0), cx);
2207    });
2208    server_cx.update(|cx| {
2209        release_channel::init(semver::Version::new(0, 0, 0), cx);
2210    });
2211    init_logger();
2212
2213    let (opts, ssh_server_client, _) = RemoteClient::fake_server(cx, server_cx);
2214    let http_client = Arc::new(BlockedHttpClient);
2215    let node_runtime = NodeRuntime::unavailable();
2216    let languages = Arc::new(LanguageRegistry::new(cx.executor()));
2217    let proxy = Arc::new(ExtensionHostProxy::new());
2218    server_cx.update(HeadlessProject::init);
2219    let headless = server_cx.new(|cx| {
2220        HeadlessProject::new(
2221            crate::HeadlessAppState {
2222                session: ssh_server_client,
2223                fs: server_fs.clone(),
2224                http_client,
2225                node_runtime,
2226                languages,
2227                extension_host_proxy: proxy,
2228                startup_time: std::time::Instant::now(),
2229            },
2230            false,
2231            cx,
2232        )
2233    });
2234
2235    let ssh = RemoteClient::connect_mock(opts, cx).await;
2236    let project = build_project(ssh, cx);
2237    project
2238        .update(cx, {
2239            let headless = headless.clone();
2240            |_, cx| cx.on_release(|_, _| drop(headless))
2241        })
2242        .detach();
2243    (project, headless)
2244}
2245
2246fn init_logger() {
2247    zlog::init_test();
2248}
2249
2250fn build_project(ssh: Entity<RemoteClient>, cx: &mut TestAppContext) -> Entity<Project> {
2251    cx.update(|cx| {
2252        if !cx.has_global::<SettingsStore>() {
2253            let settings_store = SettingsStore::test(cx);
2254            cx.set_global(settings_store);
2255        }
2256    });
2257
2258    let client = cx.update(|cx| {
2259        Client::new(
2260            Arc::new(FakeSystemClock::new()),
2261            FakeHttpClient::with_404_response(),
2262            cx,
2263        )
2264    });
2265
2266    let node = NodeRuntime::unavailable();
2267    let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
2268    let languages = Arc::new(LanguageRegistry::test(cx.executor()));
2269    let fs = FakeFs::new(cx.executor());
2270
2271    cx.update(|cx| {
2272        Project::init(&client, cx);
2273    });
2274
2275    cx.update(|cx| Project::remote(ssh, client, node, user_store, languages, fs, false, cx))
2276}