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