Finalize the command

Kirill Bulatov created

Change summary

crates/editor2/src/display_map.rs                  |  6 
crates/editor2/src/editor.rs                       |  3 
crates/editor2/src/editor_tests.rs                 |  1 
crates/editor2/src/inlay_hint_cache.rs             |  2 
crates/editor2/src/movement.rs                     |  3 
crates/editor2/src/rust_analyzer_ext.rs            | 72 ++++++++++--
crates/editor2/src/test/editor_lsp_test_context.rs |  2 
crates/project2/src/lsp_ext_command.rs             | 87 +++++++++++----
crates/rpc2/proto/zed.proto                        |  3 
9 files changed, 126 insertions(+), 53 deletions(-)

Detailed changes

crates/editor2/src/display_map.rs 🔗

@@ -997,7 +997,6 @@ pub mod tests {
         movement,
         test::{editor_test_context::EditorTestContext, marked_display_snapshot},
     };
-    use client::Client;
     use gpui::{div, font, observe, px, AppContext, Context, Element, Hsla};
     use language::{
         language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
@@ -1009,10 +1008,7 @@ pub mod tests {
     use smol::stream::StreamExt;
     use std::{env, sync::Arc};
     use theme::{LoadThemes, SyntaxTheme};
-    use util::{
-        http::FakeHttpClient,
-        test::{marked_text_ranges, sample_text},
-    };
+    use util::test::{marked_text_ranges, sample_text};
     use Bias::*;
 
     #[gpui::test(iterations = 100)]

crates/editor2/src/editor.rs 🔗

@@ -74,7 +74,7 @@ pub use multi_buffer::{
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock};
 use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
-use rand::{prelude::*, rngs::adapter};
+use rand::prelude::*;
 use rpc::proto::{self, *};
 use scroll::{
     autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
@@ -9343,7 +9343,6 @@ impl Render for Editor {
                 scrollbar_width: px(12.),
                 syntax: cx.theme().syntax().clone(),
                 diagnostic_style: cx.theme().diagnostic_style(),
-                // TODO kb find `HighlightStyle` usages
                 // todo!("what about the rest of the highlight style parts?")
                 inlays_style: HighlightStyle {
                     color: Some(cx.theme().status().hint),

crates/editor2/src/editor_tests.rs 🔗

@@ -29,7 +29,6 @@ use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
 use unindent::Unindent;
 use util::{
     assert_set_eq,
-    http::FakeHttpClient,
     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
 };
 use workspace::{

crates/editor2/src/inlay_hint_cache.rs 🔗

@@ -1202,7 +1202,6 @@ pub mod tests {
         scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
         ExcerptRange,
     };
-    use client::Client;
     use futures::StreamExt;
     use gpui::{Context, TestAppContext, View, WindowHandle};
     use itertools::Itertools;
@@ -1215,7 +1214,6 @@ pub mod tests {
     use serde_json::json;
     use settings::SettingsStore;
     use text::{Point, ToPoint};
-    use util::http::FakeHttpClient;
     use workspace::Workspace;
 
     use crate::editor_tests::update_test_language_settings;

crates/editor2/src/movement.rs 🔗

@@ -460,11 +460,10 @@ mod tests {
         test::{editor_test_context::EditorTestContext, marked_display_snapshot},
         Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
     };
-    use client::Client;
     use gpui::{font, Context as _};
     use project::Project;
     use settings::SettingsStore;
-    use util::{http::FakeHttpClient, post_inc};
+    use util::post_inc;
 
     #[gpui::test]
     fn test_previous_word_start(cx: &mut gpui::AppContext) {

crates/editor2/src/rust_analyzer_ext.rs 🔗

@@ -1,11 +1,11 @@
-use std::{path::Path, sync::Arc};
+use std::sync::Arc;
 
-use gpui::{AppContext, AsyncAppContext, Model, View, ViewContext, WindowContext};
-use language::Buffer;
-use lsp::{LanguageServer, LanguageServerId};
-use project::{lsp_command::LspCommand, lsp_ext_command::ExpandMacro, Project};
-use rpc::proto::{self, PeerId};
-use serde::{Deserialize, Serialize};
+use anyhow::Context as _;
+use gpui::{Context, Model, View, ViewContext, VisualContext, WindowContext};
+use language::Language;
+use multi_buffer::MultiBuffer;
+use project::lsp_ext_command::ExpandMacro;
+use text::ToPointUtf16;
 
 use crate::{element::register_action, Editor, ExpandMacroRecursively};
 
@@ -16,7 +16,10 @@ pub fn apply_related_actions(editor: &View<Editor>, cx: &mut WindowContext) {
             .read(cx)
             .all_buffers()
             .iter()
-            .any(|b| b.read(cx).language().map(|l| l.name()).as_deref() == Some("Rust"))
+            .any(|b| match b.read(cx).language() {
+                Some(l) => is_rust_language(l),
+                None => false,
+            })
     });
 
     if is_rust_related {
@@ -35,24 +38,39 @@ pub fn expand_macro_recursively(
     let Some(project) = &editor.project else {
         return;
     };
+    let Some(workspace) = editor.workspace() else {
+        return;
+    };
 
     let multibuffer = editor.buffer().read(cx);
 
-    let Some((trigger_anchor, server_to_query, buffer)) = editor
+    let Some((trigger_anchor, rust_language, server_to_query, buffer)) = editor
         .selections
         .disjoint_anchors()
         .into_iter()
         .filter(|selection| selection.start == selection.end)
         .filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
-        .find_map(|(buffer_id, trigger_anchor)| {
+        .filter_map(|(buffer_id, trigger_anchor)| {
             let buffer = multibuffer.buffer(buffer_id)?;
+            let rust_language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
+            if !is_rust_language(&rust_language) {
+                return None;
+            }
+            Some((trigger_anchor, rust_language, buffer))
+        })
+        .find_map(|(trigger_anchor, rust_language, buffer)| {
             project
                 .read(cx)
                 .language_servers_for_buffer(buffer.read(cx), cx)
                 .into_iter()
                 .find_map(|(adapter, server)| {
                     if adapter.name.0.as_ref() == "rust-analyzer" {
-                        Some((trigger_anchor, server.server_id(), buffer.clone()))
+                        Some((
+                            trigger_anchor,
+                            Arc::clone(&rust_language),
+                            server.server_id(),
+                            buffer.clone(),
+                        ))
                     } else {
                         None
                     }
@@ -62,14 +80,40 @@ pub fn expand_macro_recursively(
         return;
     };
 
-    let z = project.update(cx, |project, cx| {
+    let project = project.clone();
+    let buffer_snapshot = buffer.read(cx).snapshot();
+    let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot);
+    let expand_macro_task = project.update(cx, |project, cx| {
         project.request_lsp(
             buffer,
             project::LanguageServerToQuery::Other(server_to_query),
-            ExpandMacro {},
+            ExpandMacro { position },
             cx,
         )
     });
+    cx.spawn(|editor, mut cx| async move {
+        let macro_expansion = expand_macro_task.await.context("expand macro")?;
+        if macro_expansion.is_empty() {
+            log::info!("Empty macro expansion for position {position:?}");
+            return Ok(());
+        }
+
+        let buffer = project.update(&mut cx, |project, cx| {
+            project.create_buffer(&macro_expansion.expansion, Some(rust_language), cx)
+        })??;
+        workspace.update(&mut cx, |workspace, cx| {
+            let buffer = cx.build_model(|cx| {
+                MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
+            });
+            workspace.add_item(
+                Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
+                cx,
+            );
+        })
+    })
+    .detach_and_log_err(cx);
+}
 
-    // todo!("TODO kb")
+fn is_rust_language(language: &Language) -> bool {
+    language.name().as_ref() == "Rust"
 }

crates/project2/src/lsp_ext_command.rs 🔗

@@ -1,11 +1,13 @@
 use std::{path::Path, sync::Arc};
 
+use anyhow::Context;
 use async_trait::async_trait;
 use gpui::{AppContext, AsyncAppContext, Model};
-use language::Buffer;
+use language::{point_to_lsp, proto::deserialize_anchor, Buffer};
 use lsp::{LanguageServer, LanguageServerId};
 use rpc::proto::{self, PeerId};
 use serde::{Deserialize, Serialize};
+use text::{PointUtf16, ToPointUtf16};
 
 use crate::{lsp_command::LspCommand, Project};
 
@@ -31,9 +33,16 @@ pub struct ExpandedMacro {
     pub expansion: String,
 }
 
-pub struct ExpandMacro {}
+impl ExpandedMacro {
+    pub fn is_empty(&self) -> bool {
+        self.name.is_empty() && self.expansion.is_empty()
+    }
+}
+
+pub struct ExpandMacro {
+    pub position: PointUtf16,
+}
 
-// TODO kb
 #[async_trait(?Send)]
 impl LspCommand for ExpandMacro {
     type Response = ExpandedMacro;
@@ -43,55 +52,83 @@ impl LspCommand for ExpandMacro {
     fn to_lsp(
         &self,
         path: &Path,
-        buffer: &Buffer,
-        language_server: &Arc<LanguageServer>,
-        cx: &AppContext,
+        _: &Buffer,
+        _: &Arc<LanguageServer>,
+        _: &AppContext,
     ) -> ExpandMacroParams {
-        todo!()
+        ExpandMacroParams {
+            text_document: lsp::TextDocumentIdentifier {
+                uri: lsp::Url::from_file_path(path).unwrap(),
+            },
+            position: point_to_lsp(self.position),
+        }
     }
 
     async fn response_from_lsp(
         self,
         message: Option<ExpandedMacro>,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        server_id: LanguageServerId,
-        cx: AsyncAppContext,
+        _: Model<Project>,
+        _: Model<Buffer>,
+        _: LanguageServerId,
+        _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
-        anyhow::bail!("TODO kb")
+        Ok(message
+            .map(|message| ExpandedMacro {
+                name: message.name,
+                expansion: message.expansion,
+            })
+            .unwrap_or_default())
     }
 
     fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro {
-        todo!()
+        proto::LspExtExpandMacro {
+            project_id,
+            buffer_id: buffer.remote_id(),
+            position: Some(language::proto::serialize_anchor(
+                &buffer.anchor_before(self.position),
+            )),
+        }
     }
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: Model<Project>,
+        _: Model<Project>,
         buffer: Model<Buffer>,
-        cx: AsyncAppContext,
+        mut cx: AsyncAppContext,
     ) -> anyhow::Result<Self> {
-        todo!()
+        let position = message
+            .position
+            .and_then(deserialize_anchor)
+            .context("invalid position")?;
+        Ok(Self {
+            position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?,
+        })
     }
 
     fn response_to_proto(
         response: ExpandedMacro,
-        project: &mut Project,
-        peer_id: PeerId,
-        buffer_version: &clock::Global,
-        cx: &mut AppContext,
+        _: &mut Project,
+        _: PeerId,
+        _: &clock::Global,
+        _: &mut AppContext,
     ) -> proto::LspExtExpandMacroResponse {
-        todo!()
+        proto::LspExtExpandMacroResponse {
+            name: response.name,
+            expansion: response.expansion,
+        }
     }
 
     async fn response_from_proto(
         self,
         message: proto::LspExtExpandMacroResponse,
-        project: Model<Project>,
-        buffer: Model<Buffer>,
-        cx: AsyncAppContext,
+        _: Model<Project>,
+        _: Model<Buffer>,
+        _: AsyncAppContext,
     ) -> anyhow::Result<ExpandedMacro> {
-        todo!()
+        Ok(ExpandedMacro {
+            name: message.name,
+            expansion: message.expansion,
+        })
     }
 
     fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 {

crates/rpc2/proto/zed.proto 🔗

@@ -1625,7 +1625,10 @@ message Notification {
 message LspExtExpandMacro {
     uint64 project_id = 1;
     uint64 buffer_id = 2;
+    Anchor position = 3;
 }
 
 message LspExtExpandMacroResponse {
+    string name = 1;
+    string expansion = 2;
 }