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