use crate::{Event, *};use fs::FakeFs;use futures::{future, StreamExt};use gpui::{AppContext, SemanticVersion, UpdateGlobal};use http_client::Url;use language::{ language_settings::{ language_settings, AllLanguageSettings, LanguageSettingsContent, SoftWrap, }, tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,};use lsp::{DiagnosticSeverity, NumberOrString};use parking_lot::Mutex;use pretty_assertions::assert_eq;use serde_json::json;#[cfg(not(windows))]use std::os; use std::{mem, num::NonZeroU32, ops::Range, task::Poll};use task::{ResolvedTask, TaskContext};use unindent::Unindent as _;use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _}; #[gpui::test]async fn test_block_via_channel(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let (tx, mut rx) = futures::channel::mpsc::unbounded(); let _thread = std::thread::spawn(move || { std::fs::metadata("/tmp").unwrap(); std::thread::sleep(Duration::from_millis(1000)); tx.unbounded_send(1).unwrap(); }); rx.next().await.unwrap();} #[gpui::test]async fn test_block_via_smol(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let io_task = smol::unblock(move || { println!("sleeping on thread {:?}", std::thread::current().id()); std::thread::sleep(Duration::from_millis(10)); 1 }); let task = cx.foreground_executor().spawn(async move { io_task.await; }); task.await;} #[cfg(not(windows))]#[gpui::test]async fn test_symlinks(cx: &mut gpui::TestAppContext) { init_test(cx); cx.executor().allow_parking(); let dir = temp_tree(json!({ "root": { "apple": "", "banana": { "carrot": { "date": "", "endive": "", } }, "fennel": { "grape": "", } } })); let root_link_path = dir.path().join("root_link"); os::unix::fs::symlink(dir.path().join("root"), &root_link_path).unwrap(); os::unix::fs::symlink( dir.path().join("root/fennel"), dir.path().join("root/finnochio"), ) .unwrap(); let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await; project.update(cx, |project, cx| { let tree = project.worktrees(cx).next().unwrap().read(cx); assert_eq!(tree.file_count(), 5); assert_eq!( tree.inode_for_path("fennel/grape"), tree.inode_for_path("finnochio/grape") ); });} #[gpui::test]async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) { init_test(cx); let dir = temp_tree(json!({ ".editorconfig": r#" root = true [*.rs] indent_style = tab indent_size = 3 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true max_line_length = 80 [*.js] tab_width = 10 "#, ".zed": { "settings.json": r#"{ "tab_size": 8, "hard_tabs": false, "ensure_final_newline_on_save": false, "remove_trailing_whitespace_on_save": false, "preferred_line_length": 64, "soft_wrap": "editor_width" }"#, }, "a.rs": "fn a() {\n A\n}", "b": { ".editorconfig": r#" [*.rs] indent_size = 2 max_line_length = off "#, "b.rs": "fn b() {\n B\n}", }, "c.js": "def c\n C\nend", "README.json": "tabs are better\n", })); let path = dir.path(); let fs = FakeFs::new(cx.executor()); fs.insert_tree_from_real_fs(path, path).await; let project = Project::test(fs, [path], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(js_lang()); language_registry.add(json_lang()); language_registry.add(rust_lang()); let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); cx.executor().run_until_parked(); cx.update(|cx| { let tree = worktree.read(cx); let settings_for = |path: &str| { let file_entry = tree.entry_for_path(path).unwrap().clone(); let file = File::for_entry(file_entry, worktree.clone()); let file_language = project .read(cx) .languages() .language_for_file_path(file.path.as_ref()); let file_language = cx .background_executor() .block(file_language) .expect("Failed to get file language"); let file = file as _; language_settings(Some(file_language.name()), Some(&file), cx).into_owned() }; let settings_a = settings_for("a.rs"); let settings_b = settings_for("b/b.rs"); let settings_c = settings_for("c.js"); let settings_readme = settings_for("README.json"); // .editorconfig overrides .zed/settings assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3)); assert_eq!(settings_a.hard_tabs, true); assert_eq!(settings_a.ensure_final_newline_on_save, true); assert_eq!(settings_a.remove_trailing_whitespace_on_save, true); assert_eq!(settings_a.preferred_line_length, 80); // "max_line_length" also sets "soft_wrap" assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength); // .editorconfig in b/ overrides .editorconfig in root assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2)); // "indent_size" is not set, so "tab_width" is used assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10)); // When max_line_length is "off", default to .zed/settings.json assert_eq!(settings_b.preferred_line_length, 64); assert_eq!(settings_b.soft_wrap, SoftWrap::EditorWidth); // README.md should not be affected by .editorconfig's globe "*.rs" assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8)); });} #[gpui::test]async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); TaskStore::init(None); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/the-root", json!({ ".zed": { "settings.json": r#"{ "tab_size": 8 }"#, "tasks.json": r#"[{ "label": "cargo check all", "command": "cargo", "args": ["check", "--all"] },]"#, }, "a": { "a.rs": "fn a() {\n A\n}" }, "b": { ".zed": { "settings.json": r#"{ "tab_size": 2 }"#, "tasks.json": r#"[{ "label": "cargo check", "command": "cargo", "args": ["check"] },]"#, }, "b.rs": "fn b() {\n B\n}" } }), ) .await; let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); let task_context = TaskContext::default(); cx.executor().run_until_parked(); let worktree_id = cx.update(|cx| { project.update(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }) }); let topmost_local_task_source_kind = TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from(".zed"), id_base: "local worktree tasks from directory \".zed\"".into(), }; let all_tasks = cx .update(|cx| { let tree = worktree.read(cx); let file_a = File::for_entry( tree.entry_for_path("a/a.rs").unwrap().clone(), worktree.clone(), ) as _; let settings_a = language_settings(None, Some(&file_a), cx); let file_b = File::for_entry( tree.entry_for_path("b/b.rs").unwrap().clone(), worktree.clone(), ) as _; let settings_b = language_settings(None, Some(&file_b), cx); assert_eq!(settings_a.tab_size.get(), 8); assert_eq!(settings_b.tab_size.get(), 2); get_all_tasks(&project, Some(worktree_id), &task_context, cx) }) .into_iter() .map(|(source_kind, task)| { let resolved = task.resolved.unwrap(); ( source_kind, task.resolved_label, resolved.args, resolved.env, ) }) .collect::<Vec<_>>(); assert_eq!( all_tasks, vec![ ( TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from("b/.zed"), id_base: "local worktree tasks from directory \"b/.zed\"".into(), }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ( topmost_local_task_source_kind.clone(), "cargo check all".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ] ); let (_, resolved_task) = cx .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx)) .into_iter() .find(|(source_kind, _)| source_kind == &topmost_local_task_source_kind) .expect("should have one global task"); project.update(cx, |project, cx| { let task_inventory = project .task_store .read(cx) .task_inventory() .cloned() .unwrap(); task_inventory.update(cx, |inventory, _| { inventory.task_scheduled(topmost_local_task_source_kind.clone(), resolved_task); inventory .update_file_based_tasks( None, Some( &json!([{ "label": "cargo check unstable", "command": "cargo", "args": [ "check", "--all", "--all-targets" ], "env": { "RUSTFLAGS": "-Zunstable-options" } }]) .to_string(), ), ) .unwrap(); }); }); cx.run_until_parked(); let all_tasks = cx .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx)) .into_iter() .map(|(source_kind, task)| { let resolved = task.resolved.unwrap(); ( source_kind, task.resolved_label, resolved.args, resolved.env, ) }) .collect::<Vec<_>>(); assert_eq!( all_tasks, vec![ ( topmost_local_task_source_kind.clone(), "cargo check all".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ( TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from("b/.zed"), id_base: "local worktree tasks from directory \"b/.zed\"".into(), }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ( TaskSourceKind::AbsPath { abs_path: paths::tasks_file().clone(), id_base: "global tasks.json".into(), }, "cargo check unstable".to_string(), vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string(), ], HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string() ))), ), ] );} #[gpui::test]async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/the-root", json!({ "test.rs": "const A: i32 = 1;", "test2.rs": "", "Cargo.toml": "a = 1", "package.json": "{\"a\": 1}", }), ) .await; let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let mut fake_rust_servers = language_registry.register_fake_lsp( "Rust", FakeLspAdapter { name: "the-rust-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string(), "::".to_string()]), ..Default::default() }), text_document_sync: Some(lsp::TextDocumentSyncCapability::Options( lsp::TextDocumentSyncOptions { save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)), ..Default::default() }, )), ..Default::default() }, ..Default::default() }, ); let mut fake_json_servers = language_registry.register_fake_lsp( "JSON", FakeLspAdapter { name: "the-json-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..Default::default() }), text_document_sync: Some(lsp::TextDocumentSyncCapability::Options( lsp::TextDocumentSyncOptions { save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)), ..Default::default() }, )), ..Default::default() }, ..Default::default() }, ); // Open a buffer without an associated language server. let toml_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/Cargo.toml", cx) }) .await .unwrap(); // Open a buffer with an associated language server before the language for it has been loaded. let rust_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/test.rs", cx) }) .await .unwrap(); rust_buffer.update(cx, |buffer, _| { assert_eq!(buffer.language().map(|l| l.name()), None); }); // Now we add the languages to the project, and ensure they get assigned to all // the relevant open buffers. language_registry.add(json_lang()); language_registry.add(rust_lang()); cx.executor().run_until_parked(); rust_buffer.update(cx, |buffer, _| { assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); }); // A server is started up, and it is notified about Rust files. let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidOpenTextDocument>() .await .text_document, lsp::TextDocumentItem { uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), version: 0, text: "const A: i32 = 1;".to_string(), language_id: "rust".to_string(), } ); // The buffer is configured based on the language server's capabilities. rust_buffer.update(cx, |buffer, _| { assert_eq!( buffer.completion_triggers(), &[".".to_string(), "::".to_string()] ); });Lines 1–500 of 5494. Load more
use crate::{Event, *}; use fs::FakeFs; use futures::{future, StreamExt}; use gpui::{AppContext, SemanticVersion, UpdateGlobal}; use http_client::Url; use language::{ language_settings::{ language_settings, AllLanguageSettings, LanguageSettingsContent, SoftWrap, }, tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint, }; use lsp::{DiagnosticSeverity, NumberOrString}; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; #[cfg(not(windows))] use std::os; use std::{mem, num::NonZeroU32, ops::Range, task::Poll}; use task::{ResolvedTask, TaskContext}; use unindent::Unindent as _; use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _}; #[gpui::test] async fn test_block_via_channel(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let (tx, mut rx) = futures::channel::mpsc::unbounded(); let _thread = std::thread::spawn(move || { std::fs::metadata("/tmp").unwrap(); std::thread::sleep(Duration::from_millis(1000)); tx.unbounded_send(1).unwrap(); }); rx.next().await.unwrap(); } #[gpui::test] async fn test_block_via_smol(cx: &mut gpui::TestAppContext) { cx.executor().allow_parking(); let io_task = smol::unblock(move || { println!("sleeping on thread {:?}", std::thread::current().id()); std::thread::sleep(Duration::from_millis(10)); 1 }); let task = cx.foreground_executor().spawn(async move { io_task.await; }); task.await; } #[cfg(not(windows))] #[gpui::test] async fn test_symlinks(cx: &mut gpui::TestAppContext) { init_test(cx); cx.executor().allow_parking(); let dir = temp_tree(json!({ "root": { "apple": "", "banana": { "carrot": { "date": "", "endive": "", } }, "fennel": { "grape": "", } } })); let root_link_path = dir.path().join("root_link"); os::unix::fs::symlink(dir.path().join("root"), &root_link_path).unwrap(); os::unix::fs::symlink( dir.path().join("root/fennel"), dir.path().join("root/finnochio"), ) .unwrap(); let project = Project::test(Arc::new(RealFs::default()), [root_link_path.as_ref()], cx).await; project.update(cx, |project, cx| { let tree = project.worktrees(cx).next().unwrap().read(cx); assert_eq!(tree.file_count(), 5); assert_eq!( tree.inode_for_path("fennel/grape"), tree.inode_for_path("finnochio/grape") ); }); } #[gpui::test] async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) { init_test(cx); let dir = temp_tree(json!({ ".editorconfig": r#" root = true [*.rs] indent_style = tab indent_size = 3 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true max_line_length = 80 [*.js] tab_width = 10 "#, ".zed": { "settings.json": r#"{ "tab_size": 8, "hard_tabs": false, "ensure_final_newline_on_save": false, "remove_trailing_whitespace_on_save": false, "preferred_line_length": 64, "soft_wrap": "editor_width" }"#, }, "a.rs": "fn a() {\n A\n}", "b": { ".editorconfig": r#" [*.rs] indent_size = 2 max_line_length = off "#, "b.rs": "fn b() {\n B\n}", }, "c.js": "def c\n C\nend", "README.json": "tabs are better\n", })); let path = dir.path(); let fs = FakeFs::new(cx.executor()); fs.insert_tree_from_real_fs(path, path).await; let project = Project::test(fs, [path], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(js_lang()); language_registry.add(json_lang()); language_registry.add(rust_lang()); let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); cx.executor().run_until_parked(); cx.update(|cx| { let tree = worktree.read(cx); let settings_for = |path: &str| { let file_entry = tree.entry_for_path(path).unwrap().clone(); let file = File::for_entry(file_entry, worktree.clone()); let file_language = project .read(cx) .languages() .language_for_file_path(file.path.as_ref()); let file_language = cx .background_executor() .block(file_language) .expect("Failed to get file language"); let file = file as _; language_settings(Some(file_language.name()), Some(&file), cx).into_owned() }; let settings_a = settings_for("a.rs"); let settings_b = settings_for("b/b.rs"); let settings_c = settings_for("c.js"); let settings_readme = settings_for("README.json"); // .editorconfig overrides .zed/settings assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3)); assert_eq!(settings_a.hard_tabs, true); assert_eq!(settings_a.ensure_final_newline_on_save, true); assert_eq!(settings_a.remove_trailing_whitespace_on_save, true); assert_eq!(settings_a.preferred_line_length, 80); // "max_line_length" also sets "soft_wrap" assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength); // .editorconfig in b/ overrides .editorconfig in root assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2)); // "indent_size" is not set, so "tab_width" is used assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10)); // When max_line_length is "off", default to .zed/settings.json assert_eq!(settings_b.preferred_line_length, 64); assert_eq!(settings_b.soft_wrap, SoftWrap::EditorWidth); // README.md should not be affected by .editorconfig's globe "*.rs" assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8)); }); } #[gpui::test] async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); TaskStore::init(None); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/the-root", json!({ ".zed": { "settings.json": r#"{ "tab_size": 8 }"#, "tasks.json": r#"[{ "label": "cargo check all", "command": "cargo", "args": ["check", "--all"] },]"#, }, "a": { "a.rs": "fn a() {\n A\n}" }, "b": { ".zed": { "settings.json": r#"{ "tab_size": 2 }"#, "tasks.json": r#"[{ "label": "cargo check", "command": "cargo", "args": ["check"] },]"#, }, "b.rs": "fn b() {\n B\n}" } }), ) .await; let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); let task_context = TaskContext::default(); cx.executor().run_until_parked(); let worktree_id = cx.update(|cx| { project.update(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }) }); let topmost_local_task_source_kind = TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from(".zed"), id_base: "local worktree tasks from directory \".zed\"".into(), }; let all_tasks = cx .update(|cx| { let tree = worktree.read(cx); let file_a = File::for_entry( tree.entry_for_path("a/a.rs").unwrap().clone(), worktree.clone(), ) as _; let settings_a = language_settings(None, Some(&file_a), cx); let file_b = File::for_entry( tree.entry_for_path("b/b.rs").unwrap().clone(), worktree.clone(), ) as _; let settings_b = language_settings(None, Some(&file_b), cx); assert_eq!(settings_a.tab_size.get(), 8); assert_eq!(settings_b.tab_size.get(), 2); get_all_tasks(&project, Some(worktree_id), &task_context, cx) }) .into_iter() .map(|(source_kind, task)| { let resolved = task.resolved.unwrap(); ( source_kind, task.resolved_label, resolved.args, resolved.env, ) }) .collect::<Vec<_>>(); assert_eq!( all_tasks, vec![ ( TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from("b/.zed"), id_base: "local worktree tasks from directory \"b/.zed\"".into(), }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ( topmost_local_task_source_kind.clone(), "cargo check all".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ] ); let (_, resolved_task) = cx .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx)) .into_iter() .find(|(source_kind, _)| source_kind == &topmost_local_task_source_kind) .expect("should have one global task"); project.update(cx, |project, cx| { let task_inventory = project .task_store .read(cx) .task_inventory() .cloned() .unwrap(); task_inventory.update(cx, |inventory, _| { inventory.task_scheduled(topmost_local_task_source_kind.clone(), resolved_task); inventory .update_file_based_tasks( None, Some( &json!([{ "label": "cargo check unstable", "command": "cargo", "args": [ "check", "--all", "--all-targets" ], "env": { "RUSTFLAGS": "-Zunstable-options" } }]) .to_string(), ), ) .unwrap(); }); }); cx.run_until_parked(); let all_tasks = cx .update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx)) .into_iter() .map(|(source_kind, task)| { let resolved = task.resolved.unwrap(); ( source_kind, task.resolved_label, resolved.args, resolved.env, ) }) .collect::<Vec<_>>(); assert_eq!( all_tasks, vec![ ( topmost_local_task_source_kind.clone(), "cargo check all".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ( TaskSourceKind::Worktree { id: worktree_id, directory_in_worktree: PathBuf::from("b/.zed"), id_base: "local worktree tasks from directory \"b/.zed\"".into(), }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ( TaskSourceKind::AbsPath { abs_path: paths::tasks_file().clone(), id_base: "global tasks.json".into(), }, "cargo check unstable".to_string(), vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string(), ], HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string() ))), ), ] ); } #[gpui::test] async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/the-root", json!({ "test.rs": "const A: i32 = 1;", "test2.rs": "", "Cargo.toml": "a = 1", "package.json": "{\"a\": 1}", }), ) .await; let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; let language_registry = project.read_with(cx, |project, _| project.languages().clone()); let mut fake_rust_servers = language_registry.register_fake_lsp( "Rust", FakeLspAdapter { name: "the-rust-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![".".to_string(), "::".to_string()]), ..Default::default() }), text_document_sync: Some(lsp::TextDocumentSyncCapability::Options( lsp::TextDocumentSyncOptions { save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)), ..Default::default() }, )), ..Default::default() }, ..Default::default() }, ); let mut fake_json_servers = language_registry.register_fake_lsp( "JSON", FakeLspAdapter { name: "the-json-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..Default::default() }), text_document_sync: Some(lsp::TextDocumentSyncCapability::Options( lsp::TextDocumentSyncOptions { save: Some(lsp::TextDocumentSyncSaveOptions::Supported(true)), ..Default::default() }, )), ..Default::default() }, ..Default::default() }, ); // Open a buffer without an associated language server. let toml_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/Cargo.toml", cx) }) .await .unwrap(); // Open a buffer with an associated language server before the language for it has been loaded. let rust_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/test.rs", cx) }) .await .unwrap(); rust_buffer.update(cx, |buffer, _| { assert_eq!(buffer.language().map(|l| l.name()), None); }); // Now we add the languages to the project, and ensure they get assigned to all // the relevant open buffers. language_registry.add(json_lang()); language_registry.add(rust_lang()); cx.executor().run_until_parked(); rust_buffer.update(cx, |buffer, _| { assert_eq!(buffer.language().map(|l| l.name()), Some("Rust".into())); }); // A server is started up, and it is notified about Rust files. let mut fake_rust_server = fake_rust_servers.next().await.unwrap(); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidOpenTextDocument>() .await .text_document, lsp::TextDocumentItem { uri: lsp::Url::from_file_path("/the-root/test.rs").unwrap(), version: 0, text: "const A: i32 = 1;".to_string(), language_id: "rust".to_string(), } ); // The buffer is configured based on the language server's capabilities. rust_buffer.update(cx, |buffer, _| { assert_eq!( buffer.completion_triggers(), &[".".to_string(), "::".to_string()] ); });