use crate::{Event, *};use fs::FakeFs;use futures::{future, StreamExt};use gpui::{AppContext, UpdateGlobal};use language::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint,};use lsp::Url;use parking_lot::Mutex;use pretty_assertions::assert_eq;use serde_json::json;#[cfg(not(windows))]use std::os;use std::task::Poll;use task::{TaskContext, TaskTemplate, TaskTemplates};use unindent::Unindent as _;use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};use worktree::WorktreeModelHandle 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("/Users").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().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_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); 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", "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, _| project.worktrees().next().unwrap()); let task_context = TaskContext::default(); cx.executor().run_until_parked(); let workree_id = cx.update(|cx| { project.update(cx, |project, cx| { project.worktrees().next().unwrap().read(cx).id() }) }); let global_task_source_kind = TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }; cx.update(|cx| { let tree = worktree.read(cx); let settings_a = language_settings( None, Some( &(File::for_entry( tree.entry_for_path("a/a.rs").unwrap().clone(), worktree.clone(), ) as _), ), cx, ); let settings_b = language_settings( None, Some( &(File::for_entry( tree.entry_for_path("b/b.rs").unwrap().clone(), worktree.clone(), ) as _), ), cx, ); assert_eq!(settings_a.tab_size.get(), 8); assert_eq!(settings_b.tab_size.get(), 2); let all_tasks = project .update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks( None, Some(workree_id), &task_context, ); old.extend(new); old }) }) .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![ ( global_task_source_kind.clone(), "cargo check".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ( TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ] ); }); project.update(cx, |project, cx| { let inventory = project.task_inventory(); inventory.update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context); old.extend(new); let (_, resolved_task) = old .into_iter() .find(|(source_kind, _)| source_kind == &global_task_source_kind) .expect("should have one global task"); inventory.task_scheduled(global_task_source_kind.clone(), resolved_task); }) }); let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate { label: "cargo check".to_string(), command: "cargo".to_string(), args: vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string(), ], env: HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string(), ))), ..TaskTemplate::default() }])) .unwrap(); let (tx, rx) = futures::channel::mpsc::unbounded(); cx.update(|cx| { project.update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, cx| { inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json")); inventory.add_source( global_task_source_kind.clone(), |tx, cx| StaticSource::new(TrackedFile::new(rx, tx, cx)), cx, ); }); }) }); tx.unbounded_send(tasks).unwrap(); cx.run_until_parked(); cx.update(|cx| { let all_tasks = project .update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks( None, Some(workree_id), &task_context, ); old.extend(new); old }) }) .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: workree_id, abs_path: PathBuf::from("/the-root/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string() ], HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string() ))), ), ( TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ] ); });} #[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_adapter( "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() }), ..Default::default() }, ..Default::default() }, ); let mut fake_json_servers = language_registry.register_fake_lsp_adapter( "JSON", FakeLspAdapter { name: "the-json-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..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()] ); }); toml_buffer.update(cx, |buffer, _| { assert!(buffer.completion_triggers().is_empty()); }); // Edit a buffer. The changes are reported to the language server. rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx)); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidChangeTextDocument>() .await .text_document, lsp::VersionedTextDocumentIdentifier::new( lsp::Url::from_file_path("/the-root/test.rs").unwrap(), 1 ) ); // Open a third buffer with a different associated language server. let json_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/package.json", cx) }) .await .unwrap(); // A json language server is started up and is only notified about the json buffer. let mut fake_json_server = fake_json_servers.next().await.unwrap(); assert_eq!( fake_json_server .receive_notification::<lsp::notification::DidOpenTextDocument>() .await .text_document, lsp::TextDocumentItem { uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), version: 0, text: "{\"a\": 1}".to_string(), language_id: "json".to_string(), } ); // This buffer is configured based on the second language server's // capabilities. json_buffer.update(cx, |buffer, _| { assert_eq!(buffer.completion_triggers(), &[":".to_string()]); }); // When opening another buffer whose language server is already running, // it is also configured based on the existing language server's capabilities. let rust_buffer2 = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/test2.rs", cx) }) .await .unwrap(); rust_buffer2.update(cx, |buffer, _| { assert_eq!( buffer.completion_triggers(), &[".".to_string(), "::".to_string()] ); }); // Changes are reported only to servers matching the buffer's language. toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx)); rust_buffer2.update(cx, |buffer, cx| { buffer.edit([(0..0, "let x = 1;")], None, cx) }); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidChangeTextDocument>() .await .text_document, lsp::VersionedTextDocumentIdentifier::new( lsp::Url::from_file_path("/the-root/test2.rs").unwrap(), 1 ) ); // Save notifications are reported to all servers. project .update(cx, |project, cx| project.save_buffer(toml_buffer, cx)) .await .unwrap(); assert_eq!( fake_rust_serverLines 1–500 of 5029. Load more
use crate::{Event, *}; use fs::FakeFs; use futures::{future, StreamExt}; use gpui::{AppContext, UpdateGlobal}; use language::{ language_settings::{AllLanguageSettings, LanguageSettingsContent}, tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, ToPoint, }; use lsp::Url; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; #[cfg(not(windows))] use std::os; use std::task::Poll; use task::{TaskContext, TaskTemplate, TaskTemplates}; use unindent::Unindent as _; use util::{assert_set_eq, paths::PathMatcher, test::temp_tree}; use worktree::WorktreeModelHandle 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("/Users").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().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_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); 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", "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, _| project.worktrees().next().unwrap()); let task_context = TaskContext::default(); cx.executor().run_until_parked(); let workree_id = cx.update(|cx| { project.update(cx, |project, cx| { project.worktrees().next().unwrap().read(cx).id() }) }); let global_task_source_kind = TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }; cx.update(|cx| { let tree = worktree.read(cx); let settings_a = language_settings( None, Some( &(File::for_entry( tree.entry_for_path("a/a.rs").unwrap().clone(), worktree.clone(), ) as _), ), cx, ); let settings_b = language_settings( None, Some( &(File::for_entry( tree.entry_for_path("b/b.rs").unwrap().clone(), worktree.clone(), ) as _), ), cx, ); assert_eq!(settings_a.tab_size.get(), 8); assert_eq!(settings_b.tab_size.get(), 2); let all_tasks = project .update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks( None, Some(workree_id), &task_context, ); old.extend(new); old }) }) .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![ ( global_task_source_kind.clone(), "cargo check".to_string(), vec!["check".to_string(), "--all".to_string()], HashMap::default(), ), ( TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ] ); }); project.update(cx, |project, cx| { let inventory = project.task_inventory(); inventory.update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context); old.extend(new); let (_, resolved_task) = old .into_iter() .find(|(source_kind, _)| source_kind == &global_task_source_kind) .expect("should have one global task"); inventory.task_scheduled(global_task_source_kind.clone(), resolved_task); }) }); let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate { label: "cargo check".to_string(), command: "cargo".to_string(), args: vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string(), ], env: HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string(), ))), ..TaskTemplate::default() }])) .unwrap(); let (tx, rx) = futures::channel::mpsc::unbounded(); cx.update(|cx| { project.update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, cx| { inventory.remove_local_static_source(Path::new("/the-root/.zed/tasks.json")); inventory.add_source( global_task_source_kind.clone(), |tx, cx| StaticSource::new(TrackedFile::new(rx, tx, cx)), cx, ); }); }) }); tx.unbounded_send(tasks).unwrap(); cx.run_until_parked(); cx.update(|cx| { let all_tasks = project .update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { let (mut old, new) = inventory.used_and_current_resolved_tasks( None, Some(workree_id), &task_context, ); old.extend(new); old }) }) .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: workree_id, abs_path: PathBuf::from("/the-root/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec![ "check".to_string(), "--all".to_string(), "--all-targets".to_string() ], HashMap::from_iter(Some(( "RUSTFLAGS".to_string(), "-Zunstable-options".to_string() ))), ), ( TaskSourceKind::Worktree { id: workree_id, abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), id_base: "local_tasks_for_worktree", }, "cargo check".to_string(), vec!["check".to_string()], HashMap::default(), ), ] ); }); } #[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_adapter( "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() }), ..Default::default() }, ..Default::default() }, ); let mut fake_json_servers = language_registry.register_fake_lsp_adapter( "JSON", FakeLspAdapter { name: "the-json-language-server", capabilities: lsp::ServerCapabilities { completion_provider: Some(lsp::CompletionOptions { trigger_characters: Some(vec![":".to_string()]), ..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()] ); }); toml_buffer.update(cx, |buffer, _| { assert!(buffer.completion_triggers().is_empty()); }); // Edit a buffer. The changes are reported to the language server. rust_buffer.update(cx, |buffer, cx| buffer.edit([(16..16, "2")], None, cx)); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidChangeTextDocument>() .await .text_document, lsp::VersionedTextDocumentIdentifier::new( lsp::Url::from_file_path("/the-root/test.rs").unwrap(), 1 ) ); // Open a third buffer with a different associated language server. let json_buffer = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/package.json", cx) }) .await .unwrap(); // A json language server is started up and is only notified about the json buffer. let mut fake_json_server = fake_json_servers.next().await.unwrap(); assert_eq!( fake_json_server .receive_notification::<lsp::notification::DidOpenTextDocument>() .await .text_document, lsp::TextDocumentItem { uri: lsp::Url::from_file_path("/the-root/package.json").unwrap(), version: 0, text: "{\"a\": 1}".to_string(), language_id: "json".to_string(), } ); // This buffer is configured based on the second language server's // capabilities. json_buffer.update(cx, |buffer, _| { assert_eq!(buffer.completion_triggers(), &[":".to_string()]); }); // When opening another buffer whose language server is already running, // it is also configured based on the existing language server's capabilities. let rust_buffer2 = project .update(cx, |project, cx| { project.open_local_buffer("/the-root/test2.rs", cx) }) .await .unwrap(); rust_buffer2.update(cx, |buffer, _| { assert_eq!( buffer.completion_triggers(), &[".".to_string(), "::".to_string()] ); }); // Changes are reported only to servers matching the buffer's language. toml_buffer.update(cx, |buffer, cx| buffer.edit([(5..5, "23")], None, cx)); rust_buffer2.update(cx, |buffer, cx| { buffer.edit([(0..0, "let x = 1;")], None, cx) }); assert_eq!( fake_rust_server .receive_notification::<lsp::notification::DidChangeTextDocument>() .await .text_document, lsp::VersionedTextDocumentIdentifier::new( lsp::Url::from_file_path("/the-root/test2.rs").unwrap(), 1 ) ); // Save notifications are reported to all servers. project .update(cx, |project, cx| project.save_buffer(toml_buffer, cx)) .await .unwrap(); assert_eq!( fake_rust_server