Restructure fake language server to setup request handlers in advance

Max Brunsfeld , Antonio Scandurra , and Nathan Sobo created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs   |  78 +++++-----
crates/lsp/src/lsp.rs         | 242 +++++++++++++++++++++++-------------
crates/project/src/project.rs |  50 +++---
crates/server/src/rpc.rs      | 226 ++++++++++++++--------------------
4 files changed, 314 insertions(+), 282 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -5442,6 +5442,7 @@ mod tests {
     use super::*;
     use language::LanguageConfig;
     use lsp::FakeLanguageServer;
+    use postage::prelude::Stream;
     use project::{FakeFs, ProjectPath};
     use std::{cell::RefCell, rc::Rc, time::Instant};
     use text::Point;
@@ -7780,7 +7781,7 @@ mod tests {
             &mut fake,
             "/file",
             Point::new(0, 4),
-            &[
+            vec![
                 (Point::new(0, 4)..Point::new(0, 4), "first_completion"),
                 (Point::new(0, 4)..Point::new(0, 4), "second_completion"),
             ],
@@ -7842,7 +7843,7 @@ mod tests {
             &mut fake,
             "/file",
             Point::new(2, 7),
-            &[
+            vec![
                 (Point::new(2, 6)..Point::new(2, 7), "fourth_completion"),
                 (Point::new(2, 6)..Point::new(2, 7), "fifth_completion"),
                 (Point::new(2, 6)..Point::new(2, 7), "sixth_completion"),
@@ -7861,7 +7862,7 @@ mod tests {
             &mut fake,
             "/file",
             Point::new(2, 8),
-            &[
+            vec![
                 (Point::new(2, 6)..Point::new(2, 8), "fourth_completion"),
                 (Point::new(2, 6)..Point::new(2, 8), "fifth_completion"),
                 (Point::new(2, 6)..Point::new(2, 8), "sixth_completion"),
@@ -7891,47 +7892,45 @@ mod tests {
 
         async fn handle_completion_request(
             fake: &mut FakeLanguageServer,
-            path: &str,
+            path: &'static str,
             position: Point,
-            completions: &[(Range<Point>, &str)],
+            completions: Vec<(Range<Point>, &'static str)>,
         ) {
-            let (id, params) = fake.receive_request::<lsp::request::Completion>().await;
-            assert_eq!(
-                params.text_document_position.text_document.uri,
-                lsp::Url::from_file_path(path).unwrap()
-            );
-            assert_eq!(
-                params.text_document_position.position,
-                lsp::Position::new(position.row, position.column)
-            );
-
-            let completions = completions
-                .iter()
-                .map(|(range, new_text)| lsp::CompletionItem {
-                    label: new_text.to_string(),
-                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                        range: lsp::Range::new(
-                            lsp::Position::new(range.start.row, range.start.column),
-                            lsp::Position::new(range.start.row, range.start.column),
-                        ),
-                        new_text: new_text.to_string(),
-                    })),
-                    ..Default::default()
-                })
-                .collect();
-            fake.respond(id, Some(lsp::CompletionResponse::Array(completions)))
-                .await;
+            fake.handle_request::<lsp::request::Completion, _>(move |params| {
+                assert_eq!(
+                    params.text_document_position.text_document.uri,
+                    lsp::Url::from_file_path(path).unwrap()
+                );
+                assert_eq!(
+                    params.text_document_position.position,
+                    lsp::Position::new(position.row, position.column)
+                );
+                Some(lsp::CompletionResponse::Array(
+                    completions
+                        .into_iter()
+                        .map(|(range, new_text)| lsp::CompletionItem {
+                            label: new_text.to_string(),
+                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                                range: lsp::Range::new(
+                                    lsp::Position::new(range.start.row, range.start.column),
+                                    lsp::Position::new(range.start.row, range.start.column),
+                                ),
+                                new_text: new_text.to_string(),
+                            })),
+                            ..Default::default()
+                        })
+                        .collect(),
+                ))
+            })
+            .recv()
+            .await;
         }
 
         async fn handle_resolve_completion_request(
             fake: &mut FakeLanguageServer,
-            edit: Option<(Range<Point>, &str)>,
+            edit: Option<(Range<Point>, &'static str)>,
         ) {
-            let (id, _) = fake
-                .receive_request::<lsp::request::ResolveCompletionItem>()
-                .await;
-            fake.respond(
-                id,
+            fake.handle_request::<lsp::request::ResolveCompletionItem, _>(move |_| {
                 lsp::CompletionItem {
                     additional_text_edits: edit.map(|(range, new_text)| {
                         vec![lsp::TextEdit::new(
@@ -7943,8 +7942,9 @@ mod tests {
                         )]
                     }),
                     ..Default::default()
-                },
-            )
+                }
+            })
+            .recv()
             .await;
         }
     }

crates/lsp/src/lsp.rs 🔗

@@ -56,6 +56,18 @@ struct Request<'a, T> {
     params: T,
 }
 
+#[cfg(any(test, feature = "test-support"))]
+#[derive(Deserialize)]
+struct AnyRequest<'a> {
+    id: usize,
+    #[serde(borrow)]
+    jsonrpc: &'a str,
+    #[serde(borrow)]
+    method: &'a str,
+    #[serde(borrow)]
+    params: &'a RawValue,
+}
+
 #[derive(Serialize, Deserialize)]
 struct AnyResponse<'a> {
     id: usize,
@@ -469,19 +481,19 @@ impl Drop for Subscription {
 
 #[cfg(any(test, feature = "test-support"))]
 pub struct FakeLanguageServer {
-    buffer: Vec<u8>,
-    stdin: smol::io::BufReader<async_pipe::PipeReader>,
-    stdout: smol::io::BufWriter<async_pipe::PipeWriter>,
-    executor: std::rc::Rc<executor::Foreground>,
+    handlers: Arc<
+        Mutex<
+            HashMap<
+                &'static str,
+                Box<dyn Send + FnOnce(usize, &[u8]) -> (Vec<u8>, barrier::Sender)>,
+            >,
+        >,
+    >,
+    outgoing_tx: channel::Sender<Vec<u8>>,
+    incoming_rx: channel::Receiver<Vec<u8>>,
     pub started: Arc<std::sync::atomic::AtomicBool>,
 }
 
-#[cfg(any(test, feature = "test-support"))]
-pub struct RequestId<T> {
-    id: usize,
-    _type: std::marker::PhantomData<T>,
-}
-
 #[cfg(any(test, feature = "test-support"))]
 impl LanguageServer {
     pub async fn fake(cx: &gpui::TestAppContext) -> (Arc<Self>, FakeLanguageServer) {
@@ -492,28 +504,18 @@ impl LanguageServer {
         capabilities: ServerCapabilities,
         cx: &gpui::TestAppContext,
     ) -> (Arc<Self>, FakeLanguageServer) {
-        let stdin = async_pipe::pipe();
-        let stdout = async_pipe::pipe();
-        let mut fake = FakeLanguageServer {
-            stdin: smol::io::BufReader::new(stdin.1),
-            stdout: smol::io::BufWriter::new(stdout.0),
-            buffer: Vec::new(),
-            executor: cx.foreground(),
-            started: Arc::new(std::sync::atomic::AtomicBool::new(true)),
-        };
+        let (stdin_writer, stdin_reader) = async_pipe::pipe();
+        let (stdout_writer, stdout_reader) = async_pipe::pipe();
 
-        let server =
-            Self::new_internal(stdin.0, stdout.1, Path::new("/"), cx.background()).unwrap();
+        let mut fake = FakeLanguageServer::new(cx, stdin_reader, stdout_writer);
+        fake.handle_request::<request::Initialize, _>(move |_| InitializeResult {
+            capabilities,
+            ..Default::default()
+        });
 
-        let (init_id, _) = fake.receive_request::<request::Initialize>().await;
-        fake.respond(
-            init_id,
-            InitializeResult {
-                capabilities,
-                ..Default::default()
-            },
-        )
-        .await;
+        let server =
+            Self::new_internal(stdin_writer, stdout_reader, Path::new("/"), cx.background())
+                .unwrap();
         fake.receive_notification::<notification::Initialized>()
             .await;
 
@@ -523,6 +525,75 @@ impl LanguageServer {
 
 #[cfg(any(test, feature = "test-support"))]
 impl FakeLanguageServer {
+    fn new(
+        cx: &gpui::TestAppContext,
+        stdin: async_pipe::PipeReader,
+        stdout: async_pipe::PipeWriter,
+    ) -> Self {
+        use futures::StreamExt as _;
+
+        let (incoming_tx, incoming_rx) = channel::unbounded();
+        let (outgoing_tx, mut outgoing_rx) = channel::unbounded();
+        let this = Self {
+            outgoing_tx: outgoing_tx.clone(),
+            incoming_rx,
+            handlers: Default::default(),
+            started: Arc::new(std::sync::atomic::AtomicBool::new(true)),
+        };
+
+        // Receive incoming messages
+        let handlers = this.handlers.clone();
+        cx.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() {
+                    if let Ok(request) = serde_json::from_slice::<AnyRequest>(&mut buffer) {
+                        assert_eq!(request.jsonrpc, JSON_RPC_VERSION);
+
+                        let handler = handlers.lock().remove(request.method);
+                        if let Some(handler) = handler {
+                            let (response, sent) =
+                                handler(request.id, request.params.get().as_bytes());
+                            log::debug!("handled lsp request. method:{}", request.method);
+                            outgoing_tx.send(response).await.unwrap();
+                            drop(sent);
+                        } else {
+                            log::debug!("unhandled lsp request. method:{}", request.method);
+                            outgoing_tx
+                                .send(
+                                    serde_json::to_vec(&AnyResponse {
+                                        id: request.id,
+                                        error: Some(Error {
+                                            message: "no handler".to_string(),
+                                        }),
+                                        result: None,
+                                    })
+                                    .unwrap(),
+                                )
+                                .await
+                                .unwrap();
+                        }
+                    } else {
+                        incoming_tx.send(buffer.clone()).await.unwrap();
+                    }
+                }
+            })
+            .detach();
+
+        // Send outgoing messages
+        cx.background()
+            .spawn(async move {
+                let mut stdout = smol::io::BufWriter::new(stdout);
+                while let Some(notification) = outgoing_rx.next().await {
+                    Self::send(&mut stdout, &notification).await;
+                }
+            })
+            .detach();
+
+        this
+    }
+
     pub async fn notify<T: notification::Notification>(&mut self, params: T::Params) {
         if !self.started.load(std::sync::atomic::Ordering::SeqCst) {
             panic!("can't simulate an LSP notification before the server has been started");
@@ -533,54 +604,53 @@ impl FakeLanguageServer {
             params,
         })
         .unwrap();
-        self.send(message).await;
+        self.outgoing_tx.send(message).await.unwrap();
     }
 
-    pub async fn respond<'a, T: request::Request>(
-        &mut self,
-        request_id: RequestId<T>,
-        result: T::Result,
-    ) {
-        let result = serde_json::to_string(&result).unwrap();
-        let message = serde_json::to_vec(&AnyResponse {
-            id: request_id.id,
-            error: None,
-            result: Some(&RawValue::from_string(result).unwrap()),
-        })
-        .unwrap();
-        self.send(message).await;
-    }
+    pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
+        use futures::StreamExt as _;
 
-    pub async fn receive_request<T: request::Request>(&mut self) -> (RequestId<T>, T::Params) {
-        let executor = self.executor.clone();
-        executor.start_waiting();
         loop {
-            self.receive().await;
-            if let Ok(request) = serde_json::from_slice::<Request<T::Params>>(&self.buffer) {
-                assert_eq!(request.method, T::METHOD);
-                assert_eq!(request.jsonrpc, JSON_RPC_VERSION);
-                executor.finish_waiting();
-                return (
-                    RequestId {
-                        id: request.id,
-                        _type: std::marker::PhantomData,
-                    },
-                    request.params,
-                );
+            let bytes = self.incoming_rx.next().await.unwrap();
+            if let Ok(notification) = serde_json::from_slice::<Notification<T::Params>>(&bytes) {
+                assert_eq!(notification.method, T::METHOD);
+                return notification.params;
             } else {
                 log::info!(
                     "skipping message in fake language server {:?}",
-                    std::str::from_utf8(&self.buffer)
+                    std::str::from_utf8(&bytes)
                 );
             }
         }
     }
 
-    pub async fn receive_notification<T: notification::Notification>(&mut self) -> T::Params {
-        self.receive().await;
-        let notification = serde_json::from_slice::<Notification<T::Params>>(&self.buffer).unwrap();
-        assert_eq!(notification.method, T::METHOD);
-        notification.params
+    pub fn handle_request<T, F>(&mut self, handler: F) -> barrier::Receiver
+    where
+        T: 'static + request::Request,
+        F: 'static + Send + FnOnce(T::Params) -> T::Result,
+    {
+        let (responded_tx, responded_rx) = barrier::channel();
+        let prev_handler = self.handlers.lock().insert(
+            T::METHOD,
+            Box::new(|id, params| {
+                let result = handler(serde_json::from_slice::<T::Params>(params).unwrap());
+                let result = serde_json::to_string(&result).unwrap();
+                let result = serde_json::from_str::<&RawValue>(&result).unwrap();
+                let response = AnyResponse {
+                    id,
+                    error: None,
+                    result: Some(result),
+                };
+                (serde_json::to_vec(&response).unwrap(), responded_tx)
+            }),
+        );
+        if prev_handler.is_some() {
+            panic!(
+                "registered a new handler for LSP method '{}' before the previous handler was called",
+                T::METHOD
+            );
+        }
+        responded_rx
     }
 
     pub async fn start_progress(&mut self, token: impl Into<String>) {
@@ -599,39 +669,37 @@ impl FakeLanguageServer {
         .await;
     }
 
-    async fn send(&mut self, message: Vec<u8>) {
-        self.stdout
+    async fn send(stdout: &mut smol::io::BufWriter<async_pipe::PipeWriter>, message: &[u8]) {
+        stdout
             .write_all(CONTENT_LEN_HEADER.as_bytes())
             .await
             .unwrap();
-        self.stdout
+        stdout
             .write_all((format!("{}", message.len())).as_bytes())
             .await
             .unwrap();
-        self.stdout.write_all("\r\n\r\n".as_bytes()).await.unwrap();
-        self.stdout.write_all(&message).await.unwrap();
-        self.stdout.flush().await.unwrap();
+        stdout.write_all("\r\n\r\n".as_bytes()).await.unwrap();
+        stdout.write_all(&message).await.unwrap();
+        stdout.flush().await.unwrap();
     }
 
-    async fn receive(&mut self) {
-        self.buffer.clear();
-        self.stdin
-            .read_until(b'\n', &mut self.buffer)
-            .await
-            .unwrap();
-        self.stdin
-            .read_until(b'\n', &mut self.buffer)
-            .await
-            .unwrap();
-        let message_len: usize = std::str::from_utf8(&self.buffer)
+    async fn receive(
+        stdin: &mut smol::io::BufReader<async_pipe::PipeReader>,
+        buffer: &mut Vec<u8>,
+    ) -> Result<()> {
+        buffer.clear();
+        stdin.read_until(b'\n', buffer).await?;
+        stdin.read_until(b'\n', buffer).await?;
+        let message_len: usize = std::str::from_utf8(buffer)
             .unwrap()
             .strip_prefix(CONTENT_LEN_HEADER)
             .unwrap()
             .trim_end()
             .parse()
             .unwrap();
-        self.buffer.resize(message_len, 0);
-        self.stdin.read_exact(&mut self.buffer).await.unwrap();
+        buffer.resize(message_len, 0);
+        stdin.read_exact(buffer).await?;
+        Ok(())
     }
 }
 
@@ -757,9 +825,9 @@ mod tests {
             "file://b/c"
         );
 
+        fake.handle_request::<request::Shutdown, _>(|_| ());
+
         drop(server);
-        let (shutdown_request, _) = fake.receive_request::<request::Shutdown>().await;
-        fake.respond(shutdown_request, ()).await;
         fake.receive_notification::<notification::Exit>().await;
     }
 

crates/project/src/project.rs 🔗

@@ -2368,6 +2368,7 @@ impl Project {
                 .ok_or_else(|| anyhow!("invalid completion"))?,
             language,
         )?;
+        dbg!(&completion);
         cx.spawn(|this, mut cx| async move {
             match this
                 .update(&mut cx, |this, cx| {
@@ -2414,14 +2415,15 @@ impl Project {
             .and_then(language::proto::deserialize_anchor)
             .ok_or_else(|| anyhow!("invalid position"))?;
         cx.spawn(|this, mut cx| async move {
+            eprintln!("getting code actions");
             match this
                 .update(&mut cx, |this, cx| this.code_actions(&buffer, position, cx))
                 .await
             {
-                Ok(completions) => rpc.respond(
+                Ok(actions) => rpc.respond(
                     receipt,
                     proto::GetCodeActionsResponse {
-                        actions: completions
+                        actions: dbg!(actions)
                             .iter()
                             .map(language::proto::serialize_code_action)
                             .collect(),
@@ -2430,7 +2432,7 @@ impl Project {
                 Err(error) => rpc.respond_with_error(
                     receipt,
                     proto::Error {
-                        message: error.to_string(),
+                        message: dbg!(error.to_string()),
                     },
                 ),
             }
@@ -3205,6 +3207,7 @@ mod tests {
             "a.rs": "const fn a() { A }",
             "b.rs": "const y: i32 = crate::a()",
         }));
+        let dir_path = dir.path().to_path_buf();
 
         let http_client = FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone());
@@ -3229,7 +3232,6 @@ mod tests {
         cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
             .await;
 
-        // Cause worktree to start the fake language server
         let buffer = project
             .update(&mut cx, |project, cx| {
                 project.open_buffer(
@@ -3242,28 +3244,26 @@ mod tests {
             })
             .await
             .unwrap();
-        let definitions =
-            project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
-        let (request_id, request) = fake_server
-            .receive_request::<lsp::request::GotoDefinition>()
-            .await;
-        let request_params = request.text_document_position_params;
-        assert_eq!(
-            request_params.text_document.uri.to_file_path().unwrap(),
-            dir.path().join("b.rs")
-        );
-        assert_eq!(request_params.position, lsp::Position::new(0, 22));
 
-        fake_server
-            .respond(
-                request_id,
-                Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
-                    lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
-                    lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
-                ))),
-            )
-            .await;
-        let mut definitions = definitions.await.unwrap();
+        fake_server.handle_request::<lsp::request::GotoDefinition, _>(move |params| {
+            let params = params.text_document_position_params;
+            assert_eq!(
+                params.text_document.uri.to_file_path().unwrap(),
+                dir_path.join("b.rs")
+            );
+            assert_eq!(params.position, lsp::Position::new(0, 22));
+
+            Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
+                lsp::Url::from_file_path(dir_path.join("a.rs")).unwrap(),
+                lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
+            )))
+        });
+
+        let mut definitions = project
+            .update(&mut cx, |project, cx| project.definition(&buffer, 22, cx))
+            .await
+            .unwrap();
+
         assert_eq!(definitions.len(), 1);
         let definition = definitions.pop().unwrap();
         cx.update(|cx| {

crates/server/src/rpc.rs 🔗

@@ -2414,52 +2414,46 @@ mod tests {
         });
 
         // Receive a completion request as the host's language server.
-        let (request_id, params) = fake_language_server
-            .receive_request::<lsp::request::Completion>()
-            .await;
-        assert_eq!(
-            params.text_document_position.text_document.uri,
-            lsp::Url::from_file_path("/a/main.rs").unwrap(),
-        );
-        assert_eq!(
-            params.text_document_position.position,
-            lsp::Position::new(0, 14),
-        );
-
         // Return some completions from the host's language server.
-        fake_language_server
-            .respond(
-                request_id,
-                Some(lsp::CompletionResponse::Array(vec![
-                    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),
-                            ),
-                        })),
-                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-                        ..Default::default()
-                    },
-                    lsp::CompletionItem {
-                        label: "second_method(…)".into(),
-                        detail: Some("fn(&mut self, C) -> D<E>".into()),
-                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                            new_text: "second_method()".to_string(),
-                            range: lsp::Range::new(
-                                lsp::Position::new(0, 14),
-                                lsp::Position::new(0, 14),
-                            ),
-                        })),
-                        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
-                        ..Default::default()
-                    },
-                ])),
-            )
-            .await;
+        fake_language_server.handle_request::<lsp::request::Completion, _>(|params| {
+            assert_eq!(
+                params.text_document_position.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 14),
+            );
+
+            Some(lsp::CompletionResponse::Array(vec![
+                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),
+                        ),
+                    })),
+                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+                    ..Default::default()
+                },
+                lsp::CompletionItem {
+                    label: "second_method(…)".into(),
+                    detail: Some("fn(&mut self, C) -> D<E>".into()),
+                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                        new_text: "second_method()".to_string(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 14),
+                            lsp::Position::new(0, 14),
+                        ),
+                    })),
+                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+                    ..Default::default()
+                },
+            ]))
+        });
 
         // Open the buffer on the host.
         let buffer_a = project_a
@@ -2480,43 +2474,32 @@ mod tests {
             assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
         });
 
+        // Return a resolved completion from the host's language server.
+        // The resolved completion has an additional text edit.
+        fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _>(|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()
+            }
+        });
+
         buffer_a
             .condition(&cx_a, |buffer, _| {
                 buffer.text() == "fn main() { a.first_method() }"
             })
             .await;
 
-        // Receive a request resolve the selected completion on the host's language server.
-        let (request_id, params) = fake_language_server
-            .receive_request::<lsp::request::ResolveCompletionItem>()
-            .await;
-        assert_eq!(params.label, "first_method(…)");
-
-        // Return a resolved completion from the host's language server.
-        // The resolved completion has an additional text edit.
-        fake_language_server
-            .respond(
-                request_id,
-                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()
-                },
-            )
-            .await;
-
         // The additional edit is applied.
         buffer_b
             .condition(&cx_b, |buffer, _| {
@@ -2610,24 +2593,20 @@ mod tests {
         let format = project_b.update(&mut cx_b, |project, cx| {
             project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
         });
-        let (request_id, _) = fake_language_server
-            .receive_request::<lsp::request::Formatting>()
-            .await;
-        fake_language_server
-            .respond(
-                request_id,
-                Some(vec![
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
-                        new_text: "h".to_string(),
-                    },
-                    lsp::TextEdit {
-                        range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
-                        new_text: "y".to_string(),
-                    },
-                ]),
-            )
-            .await;
+
+        fake_language_server.handle_request::<lsp::request::Formatting, _>(|_| {
+            Some(vec![
+                lsp::TextEdit {
+                    range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
+                    new_text: "h".to_string(),
+                },
+                lsp::TextEdit {
+                    range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 7)),
+                    new_text: "y".to_string(),
+                },
+            ])
+        });
+
         format.await.unwrap();
         assert_eq!(
             buffer_b.read_with(&cx_b, |buffer, _| buffer.text()),
@@ -2714,26 +2693,22 @@ mod tests {
         .await
         .unwrap();
 
-        // Open the file to be formatted on client B.
+        // Open the file on client B.
         let buffer_b = cx_b
             .background()
             .spawn(project_b.update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.rs"), cx)))
             .await
             .unwrap();
 
+        // Request the definition of a symbol as the guest.
         let definitions_1 = project_b.update(&mut cx_b, |p, cx| p.definition(&buffer_b, 23, cx));
-        let (request_id, _) = fake_language_server
-            .receive_request::<lsp::request::GotoDefinition>()
-            .await;
-        fake_language_server
-            .respond(
-                request_id,
-                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)),
-                ))),
-            )
-            .await;
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_| {
+            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)),
+            )))
+        });
+
         let definitions_1 = definitions_1.await.unwrap();
         cx_b.read(|cx| {
             assert_eq!(definitions_1.len(), 1);
@@ -2752,18 +2727,13 @@ 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));
-        let (request_id, _) = fake_language_server
-            .receive_request::<lsp::request::GotoDefinition>()
-            .await;
-        fake_language_server
-            .respond(
-                request_id,
-                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)),
-                ))),
-            )
-            .await;
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_| {
+            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)),
+            )))
+        });
+
         let definitions_2 = definitions_2.await.unwrap();
         cx_b.read(|cx| {
             assert_eq!(definitions_2.len(), 1);
@@ -2887,18 +2857,12 @@ mod tests {
             definitions = project_b.update(&mut cx_b, |p, cx| p.definition(&buffer_b1, 23, cx));
         }
 
-        let (request_id, _) = fake_language_server
-            .receive_request::<lsp::request::GotoDefinition>()
-            .await;
-        fake_language_server
-            .respond(
-                request_id,
-                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)),
-                ))),
-            )
-            .await;
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _>(|_| {
+            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)),
+            )))
+        });
 
         let buffer_b2 = buffer_b2.await.unwrap();
         let definitions = definitions.await.unwrap();