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