Merge branch 'main' into selection-history

Antonio Scandurra created

Change summary

Cargo.lock                               |   1 
crates/editor/Cargo.toml                 |   1 
crates/editor/src/editor.rs              | 331 +++++++++++++++++--------
crates/editor/src/items.rs               |  19 +
crates/gpui/src/executor.rs              |   2 
crates/gpui/src/platform/mac/renderer.rs |   7 
crates/lsp/src/lsp.rs                    |  57 +++-
crates/project/src/project.rs            |   6 
crates/server/src/rpc.rs                 | 306 +++++++++++++----------
9 files changed, 457 insertions(+), 273 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1617,6 +1617,7 @@ dependencies = [
  "collections",
  "ctor",
  "env_logger",
+ "futures",
  "fuzzy",
  "gpui",
  "itertools",

crates/editor/Cargo.toml 🔗

@@ -35,6 +35,7 @@ util = { path = "../util" }
 workspace = { path = "../workspace" }
 aho-corasick = "0.7"
 anyhow = "1.0"
+futures = "0.3"
 itertools = "0.10"
 lazy_static = "1.4"
 log = "0.4"

crates/editor/src/editor.rs 🔗

@@ -69,7 +69,8 @@ action!(Backspace);
 action!(Delete);
 action!(Input, String);
 action!(Newline);
-action!(Tab);
+action!(Tab, Direction);
+action!(Indent);
 action!(Outdent);
 action!(DeleteLine);
 action!(DeleteToPreviousWordStart);
@@ -174,13 +175,15 @@ pub fn init(cx: &mut MutableAppContext) {
             Some("Editor && showing_code_actions"),
         ),
         Binding::new("enter", ConfirmRename, Some("Editor && renaming")),
-        Binding::new("tab", Tab, Some("Editor")),
+        Binding::new("tab", Tab(Direction::Next), Some("Editor")),
+        Binding::new("shift-tab", Tab(Direction::Prev), Some("Editor")),
         Binding::new(
             "tab",
             ConfirmCompletion(None),
             Some("Editor && showing_completions"),
         ),
-        Binding::new("shift-tab", Outdent, Some("Editor")),
+        Binding::new("cmd-[", Outdent, Some("Editor")),
+        Binding::new("cmd-]", Indent, Some("Editor")),
         Binding::new("ctrl-shift-K", DeleteLine, Some("Editor")),
         Binding::new("alt-backspace", DeleteToPreviousWordStart, Some("Editor")),
         Binding::new("alt-h", DeleteToPreviousWordStart, Some("Editor")),
@@ -310,6 +313,7 @@ pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(Editor::backspace);
     cx.add_action(Editor::delete);
     cx.add_action(Editor::tab);
+    cx.add_action(Editor::indent);
     cx.add_action(Editor::outdent);
     cx.add_action(Editor::delete_line);
     cx.add_action(Editor::delete_to_previous_word_start);
@@ -2961,75 +2965,101 @@ impl Editor {
         });
     }
 
-    pub fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
-        if self.move_to_next_snippet_tabstop(cx) {
-            return;
+    pub fn tab(&mut self, &Tab(direction): &Tab, cx: &mut ViewContext<Self>) {
+        match direction {
+            Direction::Prev => {
+                if !self.snippet_stack.is_empty() {
+                    self.move_to_prev_snippet_tabstop(cx);
+                    return;
+                }
+
+                self.outdent(&Outdent, cx);
+            }
+            Direction::Next => {
+                if self.move_to_next_snippet_tabstop(cx) {
+                    return;
+                }
+
+                let tab_size = cx.global::<Settings>().tab_size;
+                let mut selections = self.local_selections::<Point>(cx);
+                if selections.iter().all(|s| s.is_empty()) {
+                    self.transact(cx, |this, cx| {
+                        this.buffer.update(cx, |buffer, cx| {
+                            for selection in &mut selections {
+                                let char_column = buffer
+                                    .read(cx)
+                                    .text_for_range(
+                                        Point::new(selection.start.row, 0)..selection.start,
+                                    )
+                                    .flat_map(str::chars)
+                                    .count();
+                                let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
+                                buffer.edit(
+                                    [selection.start..selection.start],
+                                    " ".repeat(chars_to_next_tab_stop),
+                                    cx,
+                                );
+                                selection.start.column += chars_to_next_tab_stop as u32;
+                                selection.end = selection.start;
+                            }
+                        });
+                        this.update_selections(selections, Some(Autoscroll::Fit), cx);
+                    });
+                } else {
+                    self.indent(&Indent, cx);
+                }
+            }
         }
+    }
 
+    pub fn indent(&mut self, _: &Indent, cx: &mut ViewContext<Self>) {
         let tab_size = cx.global::<Settings>().tab_size;
         let mut selections = self.local_selections::<Point>(cx);
         self.transact(cx, |this, cx| {
             let mut last_indent = None;
             this.buffer.update(cx, |buffer, cx| {
                 for selection in &mut selections {
-                    if selection.is_empty() {
-                        let char_column = buffer
-                            .read(cx)
-                            .text_for_range(Point::new(selection.start.row, 0)..selection.start)
-                            .flat_map(str::chars)
-                            .count();
-                        let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
+                    let mut start_row = selection.start.row;
+                    let mut end_row = selection.end.row + 1;
+
+                    // If a selection ends at the beginning of a line, don't indent
+                    // that last line.
+                    if selection.end.column == 0 {
+                        end_row -= 1;
+                    }
+
+                    // Avoid re-indenting a row that has already been indented by a
+                    // previous selection, but still update this selection's column
+                    // to reflect that indentation.
+                    if let Some((last_indent_row, last_indent_len)) = last_indent {
+                        if last_indent_row == selection.start.row {
+                            selection.start.column += last_indent_len;
+                            start_row += 1;
+                        }
+                        if last_indent_row == selection.end.row {
+                            selection.end.column += last_indent_len;
+                        }
+                    }
+
+                    for row in start_row..end_row {
+                        let indent_column = buffer.read(cx).indent_column_for_line(row) as usize;
+                        let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
+                        let row_start = Point::new(row, 0);
                         buffer.edit(
-                            [selection.start..selection.start],
-                            " ".repeat(chars_to_next_tab_stop),
+                            [row_start..row_start],
+                            " ".repeat(columns_to_next_tab_stop),
                             cx,
                         );
-                        selection.start.column += chars_to_next_tab_stop as u32;
-                        selection.end = selection.start;
-                    } else {
-                        let mut start_row = selection.start.row;
-                        let mut end_row = selection.end.row + 1;
 
-                        // If a selection ends at the beginning of a line, don't indent
-                        // that last line.
-                        if selection.end.column == 0 {
-                            end_row -= 1;
+                        // Update this selection's endpoints to reflect the indentation.
+                        if row == selection.start.row {
+                            selection.start.column += columns_to_next_tab_stop as u32;
                         }
-
-                        // Avoid re-indenting a row that has already been indented by a
-                        // previous selection, but still update this selection's column
-                        // to reflect that indentation.
-                        if let Some((last_indent_row, last_indent_len)) = last_indent {
-                            if last_indent_row == selection.start.row {
-                                selection.start.column += last_indent_len;
-                                start_row += 1;
-                            }
-                            if last_indent_row == selection.end.row {
-                                selection.end.column += last_indent_len;
-                            }
+                        if row == selection.end.row {
+                            selection.end.column += columns_to_next_tab_stop as u32;
                         }
 
-                        for row in start_row..end_row {
-                            let indent_column =
-                                buffer.read(cx).indent_column_for_line(row) as usize;
-                            let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
-                            let row_start = Point::new(row, 0);
-                            buffer.edit(
-                                [row_start..row_start],
-                                " ".repeat(columns_to_next_tab_stop),
-                                cx,
-                            );
-
-                            // Update this selection's endpoints to reflect the indentation.
-                            if row == selection.start.row {
-                                selection.start.column += columns_to_next_tab_stop as u32;
-                            }
-                            if row == selection.end.row {
-                                selection.end.column += columns_to_next_tab_stop as u32;
-                            }
-
-                            last_indent = Some((row, columns_to_next_tab_stop as u32));
-                        }
+                        last_indent = Some((row, columns_to_next_tab_stop as u32));
                     }
                 }
             });
@@ -3039,11 +3069,6 @@ impl Editor {
     }
 
     pub fn outdent(&mut self, _: &Outdent, cx: &mut ViewContext<Self>) {
-        if !self.snippet_stack.is_empty() {
-            self.move_to_prev_snippet_tabstop(cx);
-            return;
-        }
-
         let tab_size = cx.global::<Settings>().tab_size;
         let selections = self.local_selections::<Point>(cx);
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
@@ -6427,7 +6452,7 @@ mod tests {
     use text::Point;
     use unindent::Unindent;
     use util::test::{marked_text_by, marked_text_ranges, sample_text};
-    use workspace::FollowableItem;
+    use workspace::{FollowableItem, ItemHandle};
 
     #[gpui::test]
     fn test_edit_events(cx: &mut MutableAppContext) {
@@ -7594,7 +7619,7 @@ mod tests {
             );
 
             // indent from mid-tabstop to full tabstop
-            view.tab(&Tab, cx);
+            view.tab(&Tab(Direction::Next), cx);
             assert_eq!(view.text(cx), "    one two\nthree\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
@@ -7605,7 +7630,7 @@ mod tests {
             );
 
             // outdent from 1 tabstop to 0 tabstops
-            view.outdent(&Outdent, cx);
+            view.tab(&Tab(Direction::Prev), cx);
             assert_eq!(view.text(cx), "one two\nthree\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
@@ -7619,13 +7644,13 @@ mod tests {
             view.select_display_ranges(&[DisplayPoint::new(1, 1)..DisplayPoint::new(2, 0)], cx);
 
             // indent and outdent affect only the preceding line
-            view.tab(&Tab, cx);
+            view.tab(&Tab(Direction::Next), cx);
             assert_eq!(view.text(cx), "one two\n    three\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
                 &[DisplayPoint::new(1, 5)..DisplayPoint::new(2, 0)]
             );
-            view.outdent(&Outdent, cx);
+            view.tab(&Tab(Direction::Prev), cx);
             assert_eq!(view.text(cx), "one two\nthree\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
@@ -7634,7 +7659,7 @@ mod tests {
 
             // Ensure that indenting/outdenting works when the cursor is at column 0.
             view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
-            view.tab(&Tab, cx);
+            view.tab(&Tab(Direction::Next), cx);
             assert_eq!(view.text(cx), "one two\n    three\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
@@ -7642,7 +7667,7 @@ mod tests {
             );
 
             view.select_display_ranges(&[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)], cx);
-            view.outdent(&Outdent, cx);
+            view.tab(&Tab(Direction::Prev), cx);
             assert_eq!(view.text(cx), "one two\nthree\n four");
             assert_eq!(
                 view.selected_display_ranges(cx),
@@ -8850,6 +8875,94 @@ mod tests {
         });
     }
 
+    #[gpui::test]
+    async fn test_format_during_save(cx: &mut gpui::TestAppContext) {
+        cx.foreground().forbid_parking();
+        cx.update(populate_settings);
+
+        let (mut language_server_config, mut fake_servers) = LanguageServerConfig::fake();
+        language_server_config.set_fake_capabilities(lsp::ServerCapabilities {
+            document_formatting_provider: Some(lsp::OneOf::Left(true)),
+            ..Default::default()
+        });
+        let language = Arc::new(Language::new(
+            LanguageConfig {
+                name: "Rust".into(),
+                path_suffixes: vec!["rs".to_string()],
+                language_server: Some(language_server_config),
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        ));
+
+        let fs = FakeFs::new(cx.background().clone());
+        fs.insert_file("/file.rs", Default::default()).await;
+
+        let project = Project::test(fs, cx);
+        project.update(cx, |project, _| project.languages().add(language));
+
+        let worktree_id = project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/file.rs", true, cx)
+            })
+            .await
+            .unwrap()
+            .0
+            .read_with(cx, |tree, _| tree.id());
+        let buffer = project
+            .update(cx, |project, cx| project.open_buffer((worktree_id, ""), cx))
+            .await
+            .unwrap();
+        let mut fake_server = fake_servers.next().await.unwrap();
+
+        let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
+        let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+        assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+        let save = cx.update(|cx| editor.save(project.clone(), cx));
+        fake_server
+            .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path("/file.rs").unwrap()
+                );
+                Some(vec![lsp::TextEdit::new(
+                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
+                    ", ".to_string(),
+                )])
+            })
+            .next()
+            .await;
+        save.await.unwrap();
+        assert_eq!(
+            editor.read_with(cx, |editor, cx| editor.text(cx)),
+            "one, two\nthree\n"
+        );
+        assert!(!cx.read(|cx| editor.is_dirty(cx)));
+
+        editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+        assert!(cx.read(|cx| editor.is_dirty(cx)));
+
+        // Ensure we can still save even if formatting hangs.
+        fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
+            assert_eq!(
+                params.text_document.uri,
+                lsp::Url::from_file_path("/file.rs").unwrap()
+            );
+            futures::future::pending::<()>().await;
+            unreachable!()
+        });
+        let save = cx.update(|cx| editor.save(project.clone(), cx));
+        cx.foreground().advance_clock(items::FORMAT_TIMEOUT);
+        save.await.unwrap();
+        assert_eq!(
+            editor.read_with(cx, |editor, cx| editor.text(cx)),
+            "one\ntwo\nthree\n"
+        );
+        assert!(!cx.read(|cx| editor.is_dirty(cx)));
+    }
+
     #[gpui::test]
     async fn test_completion(cx: &mut gpui::TestAppContext) {
         cx.update(populate_settings);
@@ -9031,31 +9144,34 @@ mod tests {
             position: Point,
             completions: Vec<(Range<Point>, &'static str)>,
         ) {
-            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
-                        .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.handle_request::<lsp::request::Completion, _, _>(move |params, _| {
+                let completions = completions.clone();
+                async move {
+                    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
+                            .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(),
+                    ))
+                }
             })
             .next()
             .await;
@@ -9065,18 +9181,21 @@ mod tests {
             fake: &mut FakeLanguageServer,
             edit: Option<(Range<Point>, &'static str)>,
         ) {
-            fake.handle_request::<lsp::request::ResolveCompletionItem, _>(move |_, _| {
-                lsp::CompletionItem {
-                    additional_text_edits: edit.clone().map(|(range, new_text)| {
-                        vec![lsp::TextEdit::new(
-                            lsp::Range::new(
-                                lsp::Position::new(range.start.row, range.start.column),
-                                lsp::Position::new(range.end.row, range.end.column),
-                            ),
-                            new_text.to_string(),
-                        )]
-                    }),
-                    ..Default::default()
+            fake.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _| {
+                let edit = edit.clone();
+                async move {
+                    lsp::CompletionItem {
+                        additional_text_edits: edit.map(|(range, new_text)| {
+                            vec![lsp::TextEdit::new(
+                                lsp::Range::new(
+                                    lsp::Position::new(range.start.row, range.start.column),
+                                    lsp::Position::new(range.end.row, range.end.column),
+                                ),
+                                new_text.to_string(),
+                            )]
+                        }),
+                        ..Default::default()
+                    }
                 }
             })
             .next()

crates/editor/src/items.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{Anchor, Autoscroll, Editor, Event, ExcerptId, NavigationData, ToOffset, ToPoint as _};
 use anyhow::{anyhow, Result};
+use futures::FutureExt;
 use gpui::{
     elements::*, geometry::vector::vec2f, AppContext, Entity, ModelHandle, MutableAppContext,
     RenderContext, Subscription, Task, View, ViewContext, ViewHandle,
@@ -7,13 +8,15 @@ use gpui::{
 use language::{Bias, Buffer, Diagnostic, File as _, SelectionGoal};
 use project::{File, Project, ProjectEntryId, ProjectPath};
 use rpc::proto::{self, update_view};
-use std::{fmt::Write, path::PathBuf};
+use std::{fmt::Write, path::PathBuf, time::Duration};
 use text::{Point, Selection};
-use util::ResultExt;
+use util::TryFutureExt;
 use workspace::{
     FollowableItem, Item, ItemHandle, ItemNavHistory, ProjectItem, Settings, StatusItemView,
 };
 
+pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
+
 impl FollowableItem for Editor {
     fn from_state_proto(
         pane: ViewHandle<workspace::Pane>,
@@ -317,9 +320,17 @@ impl Item for Editor {
     ) -> Task<Result<()>> {
         let buffer = self.buffer().clone();
         let buffers = buffer.read(cx).all_buffers();
-        let transaction = project.update(cx, |project, cx| project.format(buffers, true, cx));
+        let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
+        let format = project.update(cx, |project, cx| project.format(buffers, true, cx));
         cx.spawn(|this, mut cx| async move {
-            let transaction = transaction.await.log_err();
+            let transaction = futures::select_biased! {
+                _ = timeout => {
+                    log::warn!("timed out waiting for formatting");
+                    None
+                }
+                transaction = format.log_err().fuse() => transaction,
+            };
+
             this.update(&mut cx, |editor, cx| {
                 editor.request_autoscroll(Autoscroll::Fit, cx)
             });

crates/gpui/src/executor.rs 🔗

@@ -337,7 +337,7 @@ impl Deterministic {
 
             if let Some((_, wakeup_time, _)) = state.pending_timers.first() {
                 let wakeup_time = *wakeup_time;
-                if wakeup_time < new_now {
+                if wakeup_time <= new_now {
                     let timer_count = state
                         .pending_timers
                         .iter()

crates/gpui/src/platform/mac/renderer.rs 🔗

@@ -561,9 +561,10 @@ impl Renderer {
         }
 
         for icon in icons {
-            let origin = icon.bounds.origin() * scale_factor;
-            let target_size = icon.bounds.size() * scale_factor;
-            let source_size = (target_size * 2.).ceil().to_i32();
+            // Snap sprite to pixel grid.
+            let origin = (icon.bounds.origin() * scale_factor).floor();
+            let target_size = (icon.bounds.size() * scale_factor).ceil();
+            let source_size = (target_size * 2.).to_i32();
 
             let sprite =
                 self.sprite_cache

crates/lsp/src/lsp.rs 🔗

@@ -556,7 +556,14 @@ type FakeLanguageServerHandlers = Arc<
     Mutex<
         HashMap<
             &'static str,
-            Box<dyn Send + FnMut(usize, &[u8], gpui::AsyncAppContext) -> Vec<u8>>,
+            Box<
+                dyn Send
+                    + FnMut(
+                        usize,
+                        &[u8],
+                        gpui::AsyncAppContext,
+                    ) -> futures::future::BoxFuture<'static, Vec<u8>>,
+            >,
         >,
     >,
 >;
@@ -585,11 +592,16 @@ impl LanguageServer {
         let (stdout_writer, stdout_reader) = async_pipe::pipe();
 
         let mut fake = FakeLanguageServer::new(stdin_reader, stdout_writer, cx);
-        fake.handle_request::<request::Initialize, _>({
+        fake.handle_request::<request::Initialize, _, _>({
             let capabilities = capabilities.clone();
-            move |_, _| InitializeResult {
-                capabilities: capabilities.clone(),
-                ..Default::default()
+            move |_, _| {
+                let capabilities = capabilities.clone();
+                async move {
+                    InitializeResult {
+                        capabilities,
+                        ..Default::default()
+                    }
+                }
             }
         });
 
@@ -628,7 +640,8 @@ impl FakeLanguageServer {
                         let response;
                         if let Some(handler) = handlers.lock().get_mut(request.method) {
                             response =
-                                handler(request.id, request.params.get().as_bytes(), cx.clone());
+                                handler(request.id, request.params.get().as_bytes(), cx.clone())
+                                    .await;
                             log::debug!("handled lsp request. method:{}", request.method);
                         } else {
                             response = serde_json::to_vec(&AnyResponse {
@@ -704,28 +717,36 @@ impl FakeLanguageServer {
         }
     }
 
-    pub fn handle_request<T, F>(
+    pub fn handle_request<T, F, Fut>(
         &mut self,
         mut handler: F,
     ) -> futures::channel::mpsc::UnboundedReceiver<()>
     where
         T: 'static + request::Request,
-        F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> T::Result,
+        F: 'static + Send + FnMut(T::Params, gpui::AsyncAppContext) -> Fut,
+        Fut: 'static + Send + Future<Output = T::Result>,
     {
+        use futures::FutureExt as _;
+
         let (responded_tx, responded_rx) = futures::channel::mpsc::unbounded();
         self.handlers.lock().insert(
             T::METHOD,
             Box::new(move |id, params, cx| {
                 let result = handler(serde_json::from_slice::<T::Params>(params).unwrap(), cx);
-                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),
-                };
-                responded_tx.unbounded_send(()).ok();
-                serde_json::to_vec(&response).unwrap()
+                let responded_tx = responded_tx.clone();
+                async move {
+                    let result = result.await;
+                    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),
+                    };
+                    responded_tx.unbounded_send(()).ok();
+                    serde_json::to_vec(&response).unwrap()
+                }
+                .boxed()
             }),
         );
         responded_rx
@@ -844,7 +865,7 @@ mod tests {
             "file://b/c"
         );
 
-        fake.handle_request::<request::Shutdown, _>(|_, _| ());
+        fake.handle_request::<request::Shutdown, _, _>(|_, _| async move {});
 
         drop(server);
         fake.receive_notification::<notification::Exit>().await;

crates/project/src/project.rs 🔗

@@ -5789,7 +5789,7 @@ mod tests {
             .unwrap();
 
         let mut fake_server = fake_servers.next().await.unwrap();
-        fake_server.handle_request::<lsp::request::GotoDefinition, _>(move |params, _| {
+        fake_server.handle_request::<lsp::request::GotoDefinition, _, _>(|params, _| async move {
             let params = params.text_document_position_params;
             assert_eq!(
                 params.text_document.uri.to_file_path().unwrap(),
@@ -6724,7 +6724,7 @@ mod tests {
             project.prepare_rename(buffer.clone(), 7, cx)
         });
         fake_server
-            .handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
+            .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
                 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(
@@ -6743,7 +6743,7 @@ mod tests {
             project.perform_rename(buffer.clone(), 7, "THREE".to_string(), true, cx)
         });
         fake_server
-            .handle_request::<lsp::request::Rename, _>(|params, _| {
+            .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
                 assert_eq!(
                     params.text_document_position.text_document.uri.as_str(),
                     "file:///dir/one.rs"

crates/server/src/rpc.rs 🔗

@@ -2360,7 +2360,7 @@ mod tests {
         // Return some completions from the host's language server.
         cx_a.foreground().start_waiting();
         fake_language_server
-            .handle_request::<lsp::request::Completion, _>(|params, _| {
+            .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
                 assert_eq!(
                     params.text_document_position.text_document.uri,
                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
@@ -2424,8 +2424,8 @@ 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::<lsp::request::ResolveCompletionItem, _>(
-            |params, _| {
+        fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
+            |params, _| async move {
                 assert_eq!(params.label, "first_method(…)");
                 lsp::CompletionItem {
                     label: "first_method(…)".into(),
@@ -2535,7 +2535,7 @@ mod tests {
             .unwrap();
 
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        fake_language_server.handle_request::<lsp::request::Formatting, _>(|_, _| {
+        fake_language_server.handle_request::<lsp::request::Formatting, _, _>(|_, _| async move {
             Some(vec![
                 lsp::TextEdit {
                     range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
@@ -2644,12 +2644,14 @@ mod tests {
 
         // Request the definition of a symbol as the guest.
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        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)),
-            )))
-        });
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
+            |_, _| async move {
+                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 = project_b
             .update(cx_b, |p, cx| p.definition(&buffer_b, 23, cx))
@@ -2671,12 +2673,14 @@ mod tests {
 
         // Try getting more definitions for the same buffer, ensuring the buffer gets reused from
         // the previous call to `definition`.
-        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)),
-            )))
-        });
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
+            |_, _| async move {
+                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 = project_b
             .update(cx_b, |p, cx| p.definition(&buffer_b, 33, cx))
@@ -2783,26 +2787,37 @@ mod tests {
 
         // Request references to a symbol as the guest.
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        fake_language_server.handle_request::<lsp::request::References, _>(|params, _| {
-            assert_eq!(
-                params.text_document_position.text_document.uri.as_str(),
-                "file:///root-1/one.rs"
-            );
-            Some(vec![
-                lsp::Location {
-                    uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
-                    range: lsp::Range::new(lsp::Position::new(0, 24), lsp::Position::new(0, 27)),
-                },
-                lsp::Location {
-                    uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
-                    range: lsp::Range::new(lsp::Position::new(0, 35), lsp::Position::new(0, 38)),
-                },
-                lsp::Location {
-                    uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
-                    range: lsp::Range::new(lsp::Position::new(0, 37), lsp::Position::new(0, 40)),
-                },
-            ])
-        });
+        fake_language_server.handle_request::<lsp::request::References, _, _>(
+            |params, _| async move {
+                assert_eq!(
+                    params.text_document_position.text_document.uri.as_str(),
+                    "file:///root-1/one.rs"
+                );
+                Some(vec![
+                    lsp::Location {
+                        uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 24),
+                            lsp::Position::new(0, 27),
+                        ),
+                    },
+                    lsp::Location {
+                        uri: lsp::Url::from_file_path("/root-1/two.rs").unwrap(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 35),
+                            lsp::Position::new(0, 38),
+                        ),
+                    },
+                    lsp::Location {
+                        uri: lsp::Url::from_file_path("/root-2/three.rs").unwrap(),
+                        range: lsp::Range::new(
+                            lsp::Position::new(0, 37),
+                            lsp::Position::new(0, 40),
+                        ),
+                    },
+                ])
+            },
+        );
 
         let references = project_b
             .update(cx_b, |p, cx| p.references(&buffer_b, 7, cx))
@@ -3012,8 +3027,8 @@ mod tests {
 
         // Request document highlights as the guest.
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _>(
-            |params, _| {
+        fake_language_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
+            |params, _| async move {
                 assert_eq!(
                     params
                         .text_document_position_params
@@ -3158,20 +3173,22 @@ mod tests {
             .unwrap();
 
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _>(|_, _| {
-            #[allow(deprecated)]
-            Some(vec![lsp::SymbolInformation {
-                name: "TWO".into(),
-                location: lsp::Location {
-                    uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
-                    range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
-                },
-                kind: lsp::SymbolKind::CONSTANT,
-                tags: None,
-                container_name: None,
-                deprecated: None,
-            }])
-        });
+        fake_language_server.handle_request::<lsp::request::WorkspaceSymbol, _, _>(
+            |_, _| async move {
+                #[allow(deprecated)]
+                Some(vec![lsp::SymbolInformation {
+                    name: "TWO".into(),
+                    location: lsp::Location {
+                        uri: lsp::Url::from_file_path("/code/crate-2/two.rs").unwrap(),
+                        range: lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
+                    },
+                    kind: lsp::SymbolKind::CONSTANT,
+                    tags: None,
+                    container_name: None,
+                    deprecated: None,
+                }])
+            },
+        );
 
         // Request the definition of a symbol as the guest.
         let symbols = project_b
@@ -3289,12 +3306,14 @@ mod tests {
             .unwrap();
 
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
-        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)),
-            )))
-        });
+        fake_language_server.handle_request::<lsp::request::GotoDefinition, _, _>(
+            |_, _| async move {
+                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 definitions;
         let buffer_b2;
@@ -3402,7 +3421,7 @@ mod tests {
 
         let mut fake_language_server = fake_language_servers.next().await.unwrap();
         fake_language_server
-            .handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
+            .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
                 assert_eq!(
                     params.text_document.uri,
                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
@@ -3421,7 +3440,7 @@ mod tests {
         });
 
         fake_language_server
-            .handle_request::<lsp::request::CodeActionRequest, _>(|params, _| {
+            .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
                 assert_eq!(
                     params.text_document.uri,
                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
@@ -3492,41 +3511,43 @@ mod tests {
                 Editor::confirm_code_action(workspace, &ConfirmCodeAction(Some(0)), cx)
             })
             .unwrap();
-        fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _>(|_, _| {
-            lsp::CodeAction {
-                title: "Inline into all callers".to_string(),
-                edit: Some(lsp::WorkspaceEdit {
-                    changes: Some(
-                        [
-                            (
-                                lsp::Url::from_file_path("/a/main.rs").unwrap(),
-                                vec![lsp::TextEdit::new(
-                                    lsp::Range::new(
-                                        lsp::Position::new(1, 22),
-                                        lsp::Position::new(1, 34),
-                                    ),
-                                    "4".to_string(),
-                                )],
-                            ),
-                            (
-                                lsp::Url::from_file_path("/a/other.rs").unwrap(),
-                                vec![lsp::TextEdit::new(
-                                    lsp::Range::new(
-                                        lsp::Position::new(0, 0),
-                                        lsp::Position::new(0, 27),
-                                    ),
-                                    "".to_string(),
-                                )],
-                            ),
-                        ]
-                        .into_iter()
-                        .collect(),
-                    ),
+        fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
+            |_, _| async move {
+                lsp::CodeAction {
+                    title: "Inline into all callers".to_string(),
+                    edit: Some(lsp::WorkspaceEdit {
+                        changes: Some(
+                            [
+                                (
+                                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                                    vec![lsp::TextEdit::new(
+                                        lsp::Range::new(
+                                            lsp::Position::new(1, 22),
+                                            lsp::Position::new(1, 34),
+                                        ),
+                                        "4".to_string(),
+                                    )],
+                                ),
+                                (
+                                    lsp::Url::from_file_path("/a/other.rs").unwrap(),
+                                    vec![lsp::TextEdit::new(
+                                        lsp::Range::new(
+                                            lsp::Position::new(0, 0),
+                                            lsp::Position::new(0, 27),
+                                        ),
+                                        "".to_string(),
+                                    )],
+                                ),
+                            ]
+                            .into_iter()
+                            .collect(),
+                        ),
+                        ..Default::default()
+                    }),
                     ..Default::default()
-                }),
-                ..Default::default()
-            }
-        });
+                }
+            },
+        );
 
         // After the action is confirmed, an editor containing both modified files is opened.
         confirm_action.await.unwrap();
@@ -3642,7 +3663,7 @@ mod tests {
         });
 
         fake_language_server
-            .handle_request::<lsp::request::PrepareRenameRequest, _>(|params, _| {
+            .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
                 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(
@@ -3672,7 +3693,7 @@ mod tests {
             Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
         });
         fake_language_server
-            .handle_request::<lsp::request::Rename, _>(|params, _| {
+            .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
                 assert_eq!(
                     params.text_document_position.text_document.uri.as_str(),
                     "file:///dir/one.rs"
@@ -5320,30 +5341,34 @@ mod tests {
                 let files = files.clone();
                 let project = project.downgrade();
                 move |fake_server| {
-                    fake_server.handle_request::<lsp::request::Completion, _>(|_, _| {
-                        Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
-                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
-                                range: lsp::Range::new(
-                                    lsp::Position::new(0, 0),
-                                    lsp::Position::new(0, 0),
-                                ),
-                                new_text: "the-new-text".to_string(),
-                            })),
-                            ..Default::default()
-                        }]))
-                    });
-
-                    fake_server.handle_request::<lsp::request::CodeActionRequest, _>(|_, _| {
-                        Some(vec![lsp::CodeActionOrCommand::CodeAction(
-                            lsp::CodeAction {
-                                title: "the-code-action".to_string(),
+                    fake_server.handle_request::<lsp::request::Completion, _, _>(
+                        |_, _| async move {
+                            Some(lsp::CompletionResponse::Array(vec![lsp::CompletionItem {
+                                text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+                                    range: lsp::Range::new(
+                                        lsp::Position::new(0, 0),
+                                        lsp::Position::new(0, 0),
+                                    ),
+                                    new_text: "the-new-text".to_string(),
+                                })),
                                 ..Default::default()
-                            },
-                        )])
-                    });
+                            }]))
+                        },
+                    );
+
+                    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
+                        |_, _| async move {
+                            Some(vec![lsp::CodeActionOrCommand::CodeAction(
+                                lsp::CodeAction {
+                                    title: "the-code-action".to_string(),
+                                    ..Default::default()
+                                },
+                            )])
+                        },
+                    );
 
-                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _>(
-                        |params, _| {
+                    fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
+                        |params, _| async move {
                             Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
                                 params.position,
                                 params.position,
@@ -5351,34 +5376,38 @@ mod tests {
                         },
                     );
 
-                    fake_server.handle_request::<lsp::request::GotoDefinition, _>({
+                    fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
                         let files = files.clone();
                         let rng = rng.clone();
                         move |_, _| {
-                            let files = files.lock();
-                            let mut rng = rng.lock();
-                            let count = rng.gen_range::<usize, _>(1..3);
-                            let files = (0..count)
-                                .map(|_| files.choose(&mut *rng).unwrap())
-                                .collect::<Vec<_>>();
-                            log::info!("LSP: Returning definitions in files {:?}", &files);
-                            Some(lsp::GotoDefinitionResponse::Array(
-                                files
-                                    .into_iter()
-                                    .map(|file| lsp::Location {
-                                        uri: lsp::Url::from_file_path(file).unwrap(),
-                                        range: Default::default(),
-                                    })
-                                    .collect(),
-                            ))
+                            let files = files.clone();
+                            let rng = rng.clone();
+                            async move {
+                                let files = files.lock();
+                                let mut rng = rng.lock();
+                                let count = rng.gen_range::<usize, _>(1..3);
+                                let files = (0..count)
+                                    .map(|_| files.choose(&mut *rng).unwrap())
+                                    .collect::<Vec<_>>();
+                                log::info!("LSP: Returning definitions in files {:?}", &files);
+                                Some(lsp::GotoDefinitionResponse::Array(
+                                    files
+                                        .into_iter()
+                                        .map(|file| lsp::Location {
+                                            uri: lsp::Url::from_file_path(file).unwrap(),
+                                            range: Default::default(),
+                                        })
+                                        .collect(),
+                                ))
+                            }
                         }
                     });
 
-                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _>({
+                    fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
                         let rng = rng.clone();
                         let project = project.clone();
                         move |params, mut cx| {
-                            if let Some(project) = project.upgrade(&cx) {
+                            let highlights = if let Some(project) = project.upgrade(&cx) {
                                 project.update(&mut cx, |project, cx| {
                                     let path = params
                                         .text_document_position_params
@@ -5415,7 +5444,8 @@ mod tests {
                                 })
                             } else {
                                 None
-                            }
+                            };
+                            async move { highlights }
                         }
                     });
                 }