remote_editing_tests.rs

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