remote_editing_tests.rs

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