/// todo(windows)/// The tests in this file assume that server_cx is running on Windows too./// We neead to find a way to test Windows-Non-Windows interactions.use crate::headless_project::HeadlessProject;use agent::{AgentTool, ReadFileTool, ReadFileToolInput, Templates, Thread, ToolCallEventStream};use client::{Client, UserStore};use clock::FakeSystemClock;use collections::{HashMap, HashSet};use language_model::{LanguageModelToolResultContent, fake_provider::FakeLanguageModel};use prompt_store::ProjectContext; use extension::ExtensionHostProxy;use fs::{FakeFs, Fs};use gpui::{AppContext as _, Entity, SharedString, TestAppContext};use http_client::{BlockedHttpClient, FakeHttpClient};use language::{ Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, language_settings::{AllLanguageSettings, language_settings},};use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName};use node_runtime::NodeRuntime;use project::{ ProgressToken, Project, agent_server_store::AgentServerCommand, search::{SearchQuery, SearchResult},};use remote::RemoteClient;use serde_json::json;use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};use smol::stream::StreamExt;use std::{ path::{Path, PathBuf}, sync::Arc,};use unindent::Unindent as _;use util::{path, rel_path::rel_path}; #[gpui::test]async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, "project2": { "README.md": "# project 2", }, }), ) .await; fs.set_index_for_repo( Path::new(path!("/code/project1/.git")), &[("src/lib.rs", "fn one() -> usize { 0 }".into())], ); let (project, _headless) = init_test(&fs, cx, server_cx).await; let (worktree, _) = project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap(); // The client sees the worktree's contents. cx.executor().run_until_parked(); let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id()); worktree.update(cx, |worktree, _cx| { assert_eq!( worktree.paths().collect::<Vec<_>>(), vec![ rel_path("README.md"), rel_path("src"), rel_path("src/lib.rs"), ] ); }); // The user opens a buffer in the remote worktree. The buffer's // contents are loaded from the remote filesystem. let buffer = project .update(cx, |project, cx| { project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) }) .await .unwrap(); let diff = project .update(cx, |project, cx| { project.open_unstaged_diff(buffer.clone(), cx) }) .await .unwrap(); diff.update(cx, |diff, cx| { assert_eq!( diff.base_text_string(cx).unwrap(), "fn one() -> usize { 0 }" ); }); buffer.update(cx, |buffer, cx| { assert_eq!(buffer.text(), "fn one() -> usize { 1 }"); let ix = buffer.text().find('1').unwrap(); buffer.edit([(ix..ix + 1, "100")], None, cx); }); // The user saves the buffer. The new contents are written to the // remote filesystem. project .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) .await .unwrap(); assert_eq!( fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(), "fn one() -> usize { 100 }" ); // A new file is created in the remote filesystem. The user // sees the new file. fs.save( path!("/code/project1/src/main.rs").as_ref(), &"fn main() {}".into(), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); worktree.update(cx, |worktree, _cx| { assert_eq!( worktree.paths().collect::<Vec<_>>(), vec![ rel_path("README.md"), rel_path("src"), rel_path("src/lib.rs"), rel_path("src/main.rs"), ] ); }); // A file that is currently open in a buffer is renamed. fs.rename( path!("/code/project1/src/lib.rs").as_ref(), path!("/code/project1/src/lib2.rs").as_ref(), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); buffer.update(cx, |buffer, _| { assert_eq!(&**buffer.file().unwrap().path(), rel_path("src/lib2.rs")); }); fs.set_index_for_repo( Path::new(path!("/code/project1/.git")), &[("src/lib2.rs", "fn one() -> usize { 100 }".into())], ); cx.executor().run_until_parked(); diff.update(cx, |diff, cx| { assert_eq!( diff.base_text_string(cx).unwrap(), "fn one() -> usize { 100 }" ); });} #[gpui::test]async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap(); cx.run_until_parked(); async fn do_search(project: &Entity<Project>, mut cx: TestAppContext) -> Entity<Buffer> { let receiver = project.update(&mut cx, |project, cx| { project.search( SearchQuery::text( "project", false, true, false, Default::default(), Default::default(), false, None, ) .unwrap(), cx, ) }); let first_response = receiver.rx.recv().await.unwrap(); let SearchResult::Buffer { buffer, .. } = first_response else { panic!("incorrect result"); }; buffer.update(&mut cx, |buffer, cx| { assert_eq!( buffer.file().unwrap().full_path(cx).to_string_lossy(), path!("project1/README.md") ) }); assert!(receiver.rx.recv().await.is_err()); buffer } let buffer = do_search(&project, cx.clone()).await; // test that the headless server is tracking which buffers we have open correctly. cx.run_until_parked(); headless.update(server_cx, |headless, cx| { assert!(headless.buffer_store.read(cx).has_shared_buffers()) }); do_search(&project, cx.clone()).await; cx.update(|_| { drop(buffer); }); cx.run_until_parked(); headless.update(server_cx, |headless, cx| { assert!(!headless.buffer_store.read(cx).has_shared_buffers()) }); do_search(&project, cx.clone()).await;} #[gpui::test]async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( "/code", json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; cx.update_global(|settings_store: &mut SettingsStore, cx| { settings_store.set_user_settings( r#"{"languages":{"Rust":{"language_servers":["from-local-settings"]}}}"#, cx, ) }) .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { assert_eq!( AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, ["from-local-settings"], "User language settings should be synchronized with the server settings" ) }); server_cx .update_global(|settings_store: &mut SettingsStore, cx| { settings_store.set_server_settings( r#"{"languages":{"Rust":{"language_servers":["from-server-settings"]}}}"#, cx, ) }) .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { assert_eq!( AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, ["from-server-settings".to_string()], "Server language settings should take precedence over the user settings" ) }); fs.insert_tree( "/code/project1/.zed", json!({ "settings.json": r#" { "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}}, "lsp": { "override-rust-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } } } }"# }), ) .await; let worktree_id = project .update(cx, |project, cx| { project.find_or_create_worktree("/code/project1", true, cx) }) .await .unwrap() .0 .read_with(cx, |worktree, _| worktree.id()); let buffer = project .update(cx, |project, cx| { project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) }) .await .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { let worktree_id = headless .read(cx) .worktree_store .read(cx) .worktrees() .next() .unwrap() .read(cx) .id(); assert_eq!( AllLanguageSettings::get( Some(SettingsLocation { worktree_id, path: rel_path("src/lib.rs") }), cx ) .language(None, Some(&"Rust".into()), cx) .language_servers, ["override-rust-analyzer".to_string()] ) }); cx.read(|cx| { let file = buffer.read(cx).file(); assert_eq!( language_settings(Some("Rust".into()), file, cx).language_servers, ["override-rust-analyzer".to_string()] ) });} #[gpui::test]async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; fs.insert_tree( path!("/code/project1/.zed"), json!({ "settings.json": r#" { "languages": {"Rust":{"language_servers":["rust-analyzer", "fake-analyzer"]}}, "lsp": { "rust-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } }, "fake-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } } } }"# }), ) .await; cx.update_entity(&project, |project, _| { project.languages().register_test_language(LanguageConfig { name: "Rust".into(), matcher: LanguageMatcher { path_suffixes: vec!["rs".into()], ..Default::default() }, ..Default::default() }); project.languages().register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "rust-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ); project.languages().register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "fake-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ) }); let mut fake_lsp = server_cx.update(|cx| { headless.read(cx).languages.register_fake_lsp_server( LanguageServerName("rust-analyzer".into()), lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, None, ) }); let mut fake_second_lsp = server_cx.update(|cx| { headless.read(cx).languages.register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "fake-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ); headless.read(cx).languages.register_fake_lsp_server( LanguageServerName("fake-analyzer".into()), lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, None, ) }); cx.run_until_parked(); let worktree_id = project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap()Lines 1–500 of 1985. Load more
/// todo(windows) /// The tests in this file assume that server_cx is running on Windows too. /// We neead to find a way to test Windows-Non-Windows interactions. use crate::headless_project::HeadlessProject; use agent::{AgentTool, ReadFileTool, ReadFileToolInput, Templates, Thread, ToolCallEventStream}; use client::{Client, UserStore}; use clock::FakeSystemClock; use collections::{HashMap, HashSet}; use language_model::{LanguageModelToolResultContent, fake_provider::FakeLanguageModel}; use prompt_store::ProjectContext; use extension::ExtensionHostProxy; use fs::{FakeFs, Fs}; use gpui::{AppContext as _, Entity, SharedString, TestAppContext}; use http_client::{BlockedHttpClient, FakeHttpClient}; use language::{ Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, language_settings::{AllLanguageSettings, language_settings}, }; use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName}; use node_runtime::NodeRuntime; use project::{ ProgressToken, Project, agent_server_store::AgentServerCommand, search::{SearchQuery, SearchResult}, }; use remote::RemoteClient; use serde_json::json; use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content}; use smol::stream::StreamExt; use std::{ path::{Path, PathBuf}, sync::Arc, }; use unindent::Unindent as _; use util::{path, rel_path::rel_path}; #[gpui::test] async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, "project2": { "README.md": "# project 2", }, }), ) .await; fs.set_index_for_repo( Path::new(path!("/code/project1/.git")), &[("src/lib.rs", "fn one() -> usize { 0 }".into())], ); let (project, _headless) = init_test(&fs, cx, server_cx).await; let (worktree, _) = project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap(); // The client sees the worktree's contents. cx.executor().run_until_parked(); let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id()); worktree.update(cx, |worktree, _cx| { assert_eq!( worktree.paths().collect::<Vec<_>>(), vec![ rel_path("README.md"), rel_path("src"), rel_path("src/lib.rs"), ] ); }); // The user opens a buffer in the remote worktree. The buffer's // contents are loaded from the remote filesystem. let buffer = project .update(cx, |project, cx| { project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) }) .await .unwrap(); let diff = project .update(cx, |project, cx| { project.open_unstaged_diff(buffer.clone(), cx) }) .await .unwrap(); diff.update(cx, |diff, cx| { assert_eq!( diff.base_text_string(cx).unwrap(), "fn one() -> usize { 0 }" ); }); buffer.update(cx, |buffer, cx| { assert_eq!(buffer.text(), "fn one() -> usize { 1 }"); let ix = buffer.text().find('1').unwrap(); buffer.edit([(ix..ix + 1, "100")], None, cx); }); // The user saves the buffer. The new contents are written to the // remote filesystem. project .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) .await .unwrap(); assert_eq!( fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(), "fn one() -> usize { 100 }" ); // A new file is created in the remote filesystem. The user // sees the new file. fs.save( path!("/code/project1/src/main.rs").as_ref(), &"fn main() {}".into(), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); worktree.update(cx, |worktree, _cx| { assert_eq!( worktree.paths().collect::<Vec<_>>(), vec![ rel_path("README.md"), rel_path("src"), rel_path("src/lib.rs"), rel_path("src/main.rs"), ] ); }); // A file that is currently open in a buffer is renamed. fs.rename( path!("/code/project1/src/lib.rs").as_ref(), path!("/code/project1/src/lib2.rs").as_ref(), Default::default(), ) .await .unwrap(); cx.executor().run_until_parked(); buffer.update(cx, |buffer, _| { assert_eq!(&**buffer.file().unwrap().path(), rel_path("src/lib2.rs")); }); fs.set_index_for_repo( Path::new(path!("/code/project1/.git")), &[("src/lib2.rs", "fn one() -> usize { 100 }".into())], ); cx.executor().run_until_parked(); diff.update(cx, |diff, cx| { assert_eq!( diff.base_text_string(cx).unwrap(), "fn one() -> usize { 100 }" ); }); } #[gpui::test] async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap(); cx.run_until_parked(); async fn do_search(project: &Entity<Project>, mut cx: TestAppContext) -> Entity<Buffer> { let receiver = project.update(&mut cx, |project, cx| { project.search( SearchQuery::text( "project", false, true, false, Default::default(), Default::default(), false, None, ) .unwrap(), cx, ) }); let first_response = receiver.rx.recv().await.unwrap(); let SearchResult::Buffer { buffer, .. } = first_response else { panic!("incorrect result"); }; buffer.update(&mut cx, |buffer, cx| { assert_eq!( buffer.file().unwrap().full_path(cx).to_string_lossy(), path!("project1/README.md") ) }); assert!(receiver.rx.recv().await.is_err()); buffer } let buffer = do_search(&project, cx.clone()).await; // test that the headless server is tracking which buffers we have open correctly. cx.run_until_parked(); headless.update(server_cx, |headless, cx| { assert!(headless.buffer_store.read(cx).has_shared_buffers()) }); do_search(&project, cx.clone()).await; cx.update(|_| { drop(buffer); }); cx.run_until_parked(); headless.update(server_cx, |headless, cx| { assert!(!headless.buffer_store.read(cx).has_shared_buffers()) }); do_search(&project, cx.clone()).await; } #[gpui::test] async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( "/code", json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; cx.update_global(|settings_store: &mut SettingsStore, cx| { settings_store.set_user_settings( r#"{"languages":{"Rust":{"language_servers":["from-local-settings"]}}}"#, cx, ) }) .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { assert_eq!( AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, ["from-local-settings"], "User language settings should be synchronized with the server settings" ) }); server_cx .update_global(|settings_store: &mut SettingsStore, cx| { settings_store.set_server_settings( r#"{"languages":{"Rust":{"language_servers":["from-server-settings"]}}}"#, cx, ) }) .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { assert_eq!( AllLanguageSettings::get_global(cx) .language(None, Some(&"Rust".into()), cx) .language_servers, ["from-server-settings".to_string()], "Server language settings should take precedence over the user settings" ) }); fs.insert_tree( "/code/project1/.zed", json!({ "settings.json": r#" { "languages": {"Rust":{"language_servers":["override-rust-analyzer"]}}, "lsp": { "override-rust-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } } } }"# }), ) .await; let worktree_id = project .update(cx, |project, cx| { project.find_or_create_worktree("/code/project1", true, cx) }) .await .unwrap() .0 .read_with(cx, |worktree, _| worktree.id()); let buffer = project .update(cx, |project, cx| { project.open_buffer((worktree_id, rel_path("src/lib.rs")), cx) }) .await .unwrap(); cx.run_until_parked(); server_cx.read(|cx| { let worktree_id = headless .read(cx) .worktree_store .read(cx) .worktrees() .next() .unwrap() .read(cx) .id(); assert_eq!( AllLanguageSettings::get( Some(SettingsLocation { worktree_id, path: rel_path("src/lib.rs") }), cx ) .language(None, Some(&"Rust".into()), cx) .language_servers, ["override-rust-analyzer".to_string()] ) }); cx.read(|cx| { let file = buffer.read(cx).file(); assert_eq!( language_settings(Some("Rust".into()), file, cx).language_servers, ["override-rust-analyzer".to_string()] ) }); } #[gpui::test] async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext) { let fs = FakeFs::new(server_cx.executor()); fs.insert_tree( path!("/code"), json!({ "project1": { ".git": {}, "README.md": "# project 1", "src": { "lib.rs": "fn one() -> usize { 1 }" } }, }), ) .await; let (project, headless) = init_test(&fs, cx, server_cx).await; fs.insert_tree( path!("/code/project1/.zed"), json!({ "settings.json": r#" { "languages": {"Rust":{"language_servers":["rust-analyzer", "fake-analyzer"]}}, "lsp": { "rust-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } }, "fake-analyzer": { "binary": { "path": "~/.cargo/bin/rust-analyzer" } } } }"# }), ) .await; cx.update_entity(&project, |project, _| { project.languages().register_test_language(LanguageConfig { name: "Rust".into(), matcher: LanguageMatcher { path_suffixes: vec!["rs".into()], ..Default::default() }, ..Default::default() }); project.languages().register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "rust-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ); project.languages().register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "fake-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ) }); let mut fake_lsp = server_cx.update(|cx| { headless.read(cx).languages.register_fake_lsp_server( LanguageServerName("rust-analyzer".into()), lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, None, ) }); let mut fake_second_lsp = server_cx.update(|cx| { headless.read(cx).languages.register_fake_lsp_adapter( "Rust", FakeLspAdapter { name: "fake-analyzer", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, ..FakeLspAdapter::default() }, ); headless.read(cx).languages.register_fake_lsp_server( LanguageServerName("fake-analyzer".into()), lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions::default()), rename_provider: Some(lsp::OneOf::Left(true)), ..lsp::ServerCapabilities::default() }, None, ) }); cx.run_until_parked(); let worktree_id = project .update(cx, |project, cx| { project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await .unwrap()