diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 254f2ee4150609fb9a0faeae6b02031ae78d6582..d3c6beaa9751fefd23b865345caebaf927f0a86e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8143,16 +8143,18 @@ mod tests { #[gpui::test] async fn test_completion(mut cx: gpui::TestAppContext) { let settings = cx.read(EditorSettings::test); - let (language_server, mut fake) = lsp::LanguageServer::fake_with_capabilities( - lsp::ServerCapabilities { - completion_provider: Some(lsp::CompletionOptions { - trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + let (language_server, mut fake) = cx.update(|cx| { + lsp::LanguageServer::fake_with_capabilities( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }, - cx.background(), - ); + }, + cx, + ) + }); let text = " one @@ -8318,7 +8320,7 @@ mod tests { position: Point, completions: Vec<(Range, &'static str)>, ) { - fake.handle_request::(move |params| { + fake.handle_request::(move |params, _| { assert_eq!( params.text_document_position.text_document.uri, lsp::Url::from_file_path(path).unwrap() @@ -8352,7 +8354,7 @@ mod tests { fake: &mut FakeLanguageServer, edit: Option<(Range, &'static str)>, ) { - fake.handle_request::(move |_| { + fake.handle_request::(move |_, _| { lsp::CompletionItem { additional_text_edits: edit.clone().map(|(range, new_text)| { vec![lsp::TextEdit::new( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 57a4fe001a7228767e29f261b064451bcc6f8ceb..31edba7131c61776ae9bc4fac7bec3aa00211a65 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -13,7 +13,7 @@ use futures::{ future::{BoxFuture, Shared}, FutureExt, TryFutureExt, }; -use gpui::{AppContext, Task}; +use gpui::{MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; @@ -225,7 +225,7 @@ impl LanguageRegistry { language: &Arc, root_path: Arc, http_client: Arc, - cx: &AppContext, + cx: &mut MutableAppContext, ) -> Option>>> { #[cfg(any(test, feature = "test-support"))] if let Some(config) = &language.config.language_server { @@ -234,7 +234,7 @@ impl LanguageRegistry { let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities( fake_config.capabilities.clone(), - cx.background().clone(), + cx, ); if let Some(initalizer) = &fake_config.initializer { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 9fd5d693ff65de0de342d6c2b3ac86d70d5d188c..eac6c6b5d6be0741ae750768ec315d16790a9a5f 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -554,7 +554,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte #[gpui::test] async fn test_diagnostics(mut cx: gpui::TestAppContext) { - let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()); + let (language_server, mut fake) = cx.update(lsp::LanguageServer::fake); let mut rust_lang = rust_lang(); rust_lang.config.language_server = Some(LanguageServerConfig { disk_based_diagnostic_sources: HashSet::from_iter(["disk".to_string()]), @@ -837,7 +837,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { #[gpui::test] async fn test_edits_from_lsp_with_past_version(mut cx: gpui::TestAppContext) { - let (language_server, mut fake) = lsp::LanguageServer::fake(cx.background()); + let (language_server, mut fake) = cx.update(lsp::LanguageServer::fake); let text = " fn a() { diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 93df14f89dd9b08c849b074d6ddbd00f068de5b0..179a87e43a825afba9bbde96a5979c44b9d80d7f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context, Result}; use futures::{io::BufWriter, AsyncRead, AsyncWrite}; -use gpui::{executor, Task}; +use gpui::{executor, AsyncAppContext, Task}; use parking_lot::{Mutex, RwLock}; use postage::{barrier, oneshot, prelude::Stream, sink::Sink, watch}; use serde::{Deserialize, Serialize}; @@ -483,36 +483,44 @@ impl Drop for Subscription { #[cfg(any(test, feature = "test-support"))] pub struct FakeLanguageServer { - handlers: - Arc Vec>>>>, + handlers: Arc< + Mutex< + HashMap<&'static str, Box Vec>>, + >, + >, outgoing_tx: futures::channel::mpsc::UnboundedSender>, incoming_rx: futures::channel::mpsc::UnboundedReceiver>, } #[cfg(any(test, feature = "test-support"))] impl LanguageServer { - pub fn fake(executor: Arc) -> (Arc, FakeLanguageServer) { - Self::fake_with_capabilities(Default::default(), executor) + pub fn fake(cx: &mut gpui::MutableAppContext) -> (Arc, FakeLanguageServer) { + Self::fake_with_capabilities(Default::default(), cx) } pub fn fake_with_capabilities( capabilities: ServerCapabilities, - executor: Arc, + cx: &mut gpui::MutableAppContext, ) -> (Arc, FakeLanguageServer) { let (stdin_writer, stdin_reader) = async_pipe::pipe(); let (stdout_writer, stdout_reader) = async_pipe::pipe(); - let mut fake = FakeLanguageServer::new(executor.clone(), stdin_reader, stdout_writer); + let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx); fake.handle_request::({ let capabilities = capabilities.clone(); - move |_| InitializeResult { + move |_, _| InitializeResult { capabilities: capabilities.clone(), ..Default::default() } }); - let server = - Self::new_internal(stdin_writer, stdout_reader, Path::new("/"), executor).unwrap(); + let server = Self::new_internal( + stdin_writer, + stdout_reader, + Path::new("/"), + cx.background().clone(), + ) + .unwrap(); (server, fake) } @@ -521,9 +529,9 @@ impl LanguageServer { #[cfg(any(test, feature = "test-support"))] impl FakeLanguageServer { fn new( - background: Arc, stdin: async_pipe::PipeReader, stdout: async_pipe::PipeWriter, + cx: &mut gpui::MutableAppContext, ) -> Self { use futures::StreamExt as _; @@ -537,43 +545,42 @@ impl FakeLanguageServer { // Receive incoming messages let handlers = this.handlers.clone(); - let executor = background.clone(); - background - .spawn(async move { - let mut buffer = Vec::new(); - let mut stdin = smol::io::BufReader::new(stdin); - while Self::receive(&mut stdin, &mut buffer).await.is_ok() { - executor.simulate_random_delay().await; - if let Ok(request) = serde_json::from_slice::(&buffer) { - assert_eq!(request.jsonrpc, JSON_RPC_VERSION); - - if let Some(handler) = handlers.lock().get_mut(request.method) { - let response = handler(request.id, request.params.get().as_bytes()); - log::debug!("handled lsp request. method:{}", request.method); - outgoing_tx.unbounded_send(response)?; - } else { - log::debug!("unhandled lsp request. method:{}", request.method); - outgoing_tx.unbounded_send( - serde_json::to_vec(&AnyResponse { - id: request.id, - error: Some(Error { - message: "no handler".to_string(), - }), - result: None, - }) - .unwrap(), - )?; - } + cx.spawn(|cx| async move { + let mut buffer = Vec::new(); + let mut stdin = smol::io::BufReader::new(stdin); + while Self::receive(&mut stdin, &mut buffer).await.is_ok() { + cx.background().simulate_random_delay().await; + if let Ok(request) = serde_json::from_slice::(&buffer) { + assert_eq!(request.jsonrpc, JSON_RPC_VERSION); + + if let Some(handler) = handlers.lock().get_mut(request.method) { + let response = + handler(request.id, request.params.get().as_bytes(), cx.clone()); + log::debug!("handled lsp request. method:{}", request.method); + outgoing_tx.unbounded_send(response)?; } else { - incoming_tx.unbounded_send(buffer.clone())?; + log::debug!("unhandled lsp request. method:{}", request.method); + outgoing_tx.unbounded_send( + serde_json::to_vec(&AnyResponse { + id: request.id, + error: Some(Error { + message: "no handler".to_string(), + }), + result: None, + }) + .unwrap(), + )?; } + } else { + incoming_tx.unbounded_send(buffer.clone())?; } - Ok::<_, anyhow::Error>(()) - }) - .detach(); + } + Ok::<_, anyhow::Error>(()) + }) + .detach(); // Send outgoing messages - background + cx.background() .spawn(async move { let mut stdout = smol::io::BufWriter::new(stdout); while let Some(notification) = outgoing_rx.next().await { @@ -618,13 +625,13 @@ impl FakeLanguageServer { ) -> futures::channel::mpsc::UnboundedReceiver<()> where T: 'static + request::Request, - F: 'static + Send + Sync + FnMut(T::Params) -> T::Result, + F: 'static + Send + FnMut(T::Params, AsyncAppContext) -> T::Result, { let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded(); self.handlers.lock().insert( T::METHOD, - Box::new(move |id, params| { - let result = handler(serde_json::from_slice::(params).unwrap()); + Box::new(move |id, params, cx| { + let result = handler(serde_json::from_slice::(params).unwrap(), cx); let result = serde_json::to_string(&result).unwrap(); let result = serde_json::from_str::<&RawValue>(&result).unwrap(); let response = AnyResponse { @@ -709,8 +716,8 @@ mod tests { } #[gpui::test] - async fn test_fake(cx: TestAppContext) { - let (server, mut fake) = LanguageServer::fake(cx.background()); + async fn test_fake(mut cx: TestAppContext) { + let (server, mut fake) = cx.update(LanguageServer::fake); let (message_tx, message_rx) = channel::unbounded(); let (diagnostics_tx, diagnostics_rx) = channel::unbounded(); @@ -762,7 +769,7 @@ mod tests { "file://b/c" ); - fake.handle_request::(|_| ()); + fake.handle_request::(|_, _| ()); drop(server); fake.receive_notification::().await; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4e67b11a59f5dec31c266a0b0527dae128a02d71..ee19d4a023544561c76f76f96502e44fc98f2d7a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3612,7 +3612,7 @@ mod tests { .unwrap(); let mut fake_server = fake_servers.next().await.unwrap(); - fake_server.handle_request::(move |params| { + fake_server.handle_request::(move |params, _| { let params = params.text_document_position_params; assert_eq!( params.text_document.uri.to_file_path().unwrap(), @@ -4504,7 +4504,7 @@ mod tests { project.prepare_rename(buffer.clone(), 7, cx) }); fake_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); assert_eq!(params.position, lsp::Position::new(0, 7)); Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( @@ -4523,7 +4523,7 @@ mod tests { project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx) }); fake_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!( params.text_document_position.text_document.uri.as_str(), "file:///dir/one.rs" diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index a767676fad553b4c5d42ebc439553afc2b6f4e2a..2e0b0892a17d0c4c5488aac3b708cec7ebf961f8 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2389,7 +2389,7 @@ mod tests { // Return some completions from the host's language server. cx_a.foreground().start_waiting(); fake_language_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!( params.text_document_position.text_document.uri, lsp::Url::from_file_path("/a/main.rs").unwrap(), @@ -2455,23 +2455,28 @@ mod tests { // Return a resolved completion from the host's language server. // The resolved completion has an additional text edit. - fake_language_server.handle_request::(|params| { - assert_eq!(params.label, "first_method(…)"); - lsp::CompletionItem { - label: "first_method(…)".into(), - detail: Some("fn(&mut self, B) -> C".into()), - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - new_text: "first_method($1)".to_string(), - range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), - })), - additional_text_edits: Some(vec![lsp::TextEdit { - new_text: "use d::SomeTrait;\n".to_string(), - range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), - }]), - insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), - ..Default::default() - } - }); + fake_language_server.handle_request::( + |params, _| { + assert_eq!(params.label, "first_method(…)"); + lsp::CompletionItem { + label: "first_method(…)".into(), + detail: Some("fn(&mut self, B) -> C".into()), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + new_text: "first_method($1)".to_string(), + range: lsp::Range::new( + lsp::Position::new(0, 14), + lsp::Position::new(0, 14), + ), + })), + additional_text_edits: Some(vec![lsp::TextEdit { + new_text: "use d::SomeTrait;\n".to_string(), + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), + }]), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + ..Default::default() + } + }, + ); // The additional edit is applied. buffer_a @@ -2568,7 +2573,7 @@ mod tests { }); let mut fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { Some(vec![ lsp::TextEdit { range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)), @@ -2677,7 +2682,7 @@ mod tests { let definitions_1 = project_b.update(&mut cx_b, |p, cx| p.definition(&buffer_b, 23, cx)); let mut fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new( lsp::Url::from_file_path("/root-2/b.rs").unwrap(), lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)), @@ -2702,7 +2707,7 @@ mod tests { // Try getting more definitions for the same buffer, ensuring the buffer gets reused from // the previous call to `definition`. let definitions_2 = project_b.update(&mut cx_b, |p, cx| p.definition(&buffer_b, 33, cx)); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new( lsp::Url::from_file_path("/root-2/b.rs").unwrap(), lsp::Range::new(lsp::Position::new(1, 6), lsp::Position::new(1, 11)), @@ -2826,7 +2831,7 @@ mod tests { let references = project_b.update(&mut cx_b, |p, cx| p.references(&buffer_b, 7, cx)); let mut fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::(|params| { + fake_language_server.handle_request::(|params, _| { assert_eq!( params.text_document_position.text_document.uri.as_str(), "file:///root-1/one.rs" @@ -2954,7 +2959,7 @@ mod tests { let mut fake_language_server = fake_language_servers.next().await.unwrap(); fake_language_server.handle_request::( - |params| { + |params, _| { assert_eq!( params .text_document_position_params @@ -3103,7 +3108,7 @@ mod tests { // Request the definition of a symbol as the guest. let symbols = project_b.update(&mut cx_b, |p, cx| p.symbols("two", cx)); let mut fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { #[allow(deprecated)] Some(vec![lsp::SymbolInformation { name: "TWO".into(), @@ -3245,7 +3250,7 @@ mod tests { } let mut fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new( lsp::Url::from_file_path("/root/b.rs").unwrap(), lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)), @@ -3353,7 +3358,7 @@ mod tests { let mut fake_language_server = fake_language_servers.next().await.unwrap(); fake_language_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!( params.text_document.uri, lsp::Url::from_file_path("/a/main.rs").unwrap(), @@ -3372,7 +3377,7 @@ mod tests { }); fake_language_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!( params.text_document.uri, lsp::Url::from_file_path("/a/main.rs").unwrap(), @@ -3443,7 +3448,7 @@ mod tests { Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx) }) .unwrap(); - fake_language_server.handle_request::(|_| { + fake_language_server.handle_request::(|_, _| { lsp::CodeAction { title: "Inline into all callers".to_string(), edit: Some(lsp::WorkspaceEdit { @@ -3598,7 +3603,7 @@ mod tests { }); fake_language_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); assert_eq!(params.position, lsp::Position::new(0, 7)); Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( @@ -3628,7 +3633,7 @@ mod tests { Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap() }); fake_language_server - .handle_request::(|params| { + .handle_request::(|params, _| { assert_eq!( params.text_document_position.text_document.uri.as_str(), "file:///dir/one.rs" @@ -4684,7 +4689,7 @@ mod tests { let rng = rng.clone(); let files = files.clone(); move |fake_server| { - fake_server.handle_request::(|_| { + fake_server.handle_request::(|_, _| { Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem { text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { range: lsp::Range::new( @@ -4697,7 +4702,7 @@ mod tests { }])) }); - fake_server.handle_request::(|_| { + fake_server.handle_request::(|_, _| { Some(vec![lsp::CodeActionOrCommand::CodeAction( lsp::CodeAction { title: "the-code-action".to_string(), @@ -4706,17 +4711,19 @@ mod tests { )]) }); - fake_server.handle_request::(|params| { - Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( - params.position, - params.position, - ))) - }); + fake_server.handle_request::( + |params, _| { + Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + params.position, + params.position, + ))) + }, + ); fake_server.handle_request::({ let files = files.clone(); let rng = rng.clone(); - move |_| { + move |_, _| { let files = files.lock(); let mut rng = rng.lock(); let count = rng.gen_range::(1..3);