From e3fc810b3d588c99ab03a53f052b77ff5accc91e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 11 Dec 2023 02:29:32 +0200 Subject: [PATCH 1/3] Draft an expand macro recusively command --- crates/editor2/src/display_map.rs | 6 +- crates/editor2/src/editor.rs | 6 +- crates/editor2/src/editor_tests.rs | 1 + crates/editor2/src/element.rs | 6 +- crates/editor2/src/inlay_hint_cache.rs | 2 + crates/editor2/src/movement.rs | 3 +- crates/editor2/src/rust_analyzer_ext.rs | 75 +++++++++++++ .../src/test/editor_lsp_test_context.rs | 2 + crates/project2/src/lsp_command.rs | 2 +- crates/project2/src/lsp_ext_command.rs | 100 ++++++++++++++++++ crates/project2/src/project2.rs | 8 +- crates/rpc2/proto/zed.proto | 12 ++- crates/rpc2/src/proto.rs | 4 + 13 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 crates/editor2/src/rust_analyzer_ext.rs create mode 100644 crates/project2/src/lsp_ext_command.rs diff --git a/crates/editor2/src/display_map.rs b/crates/editor2/src/display_map.rs index 60975a7a5caec6552b3154e528b9629e9beb094c..dde063dab22c36d2b3da531681a2f80dd8e54bc6 100644 --- a/crates/editor2/src/display_map.rs +++ b/crates/editor2/src/display_map.rs @@ -997,6 +997,7 @@ 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}, @@ -1008,7 +1009,10 @@ pub mod tests { use smol::stream::StreamExt; use std::{env, sync::Arc}; use theme::{LoadThemes, SyntaxTheme}; - use util::test::{marked_text_ranges, sample_text}; + use util::{ + http::FakeHttpClient, + test::{marked_text_ranges, sample_text}, + }; use Bias::*; #[gpui::test(iterations = 100)] diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 4912e1aa117a6566224986df7c8fb2dfb3c88446..907a2e21535962360d46bed5b9dd9cfaec22b44c 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -13,6 +13,7 @@ mod link_go_to_definition; mod mouse_context_menu; pub mod movement; mod persistence; +mod rust_analyzer_ext; pub mod scroll; pub mod selections_collection; @@ -73,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::*; +use rand::{prelude::*, rngs::adapter}; use rpc::proto::{self, *}; use scroll::{ autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide, @@ -107,7 +108,7 @@ use ui::{ use ui::{prelude::*, IconSize}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ - item::{ItemEvent, ItemHandle}, + item::{Item, ItemEvent, ItemHandle}, searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace, }; @@ -329,6 +330,7 @@ actions!( DeleteToPreviousSubwordStart, DeleteToPreviousWordStart, DuplicateLine, + ExpandMacroRecursively, FindAllReferences, Fold, FoldSelectedRanges, diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 2548fc40a4afc2262a533536a6230c650183b1f0..193ea5bc4b7eef3ec2d294109102a9d93111293e 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -29,6 +29,7 @@ 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::{ diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index fb7d84e17338d8a2b1812e818440b6a70b285fad..4e444ee18924aea6053fdb5553f0b941e12ef6db 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -32,7 +32,7 @@ use gpui::{ Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine, }; use itertools::Itertools; -use language::language_settings::ShowWhitespaceSetting; +use language::{language_settings::ShowWhitespaceSetting, Language}; use multi_buffer::Anchor; use project::{ project_settings::{GitGutterSetting, ProjectSettings}, @@ -135,11 +135,13 @@ impl EditorElement { fn register_actions(&self, cx: &mut WindowContext) { let view = &self.editor; - self.editor.update(cx, |editor, cx| { + view.update(cx, |editor, cx| { for action in editor.editor_actions.iter() { (action)(cx) } }); + + crate::rust_analyzer_ext::apply_related_actions(view, cx); register_action(view, cx, Editor::move_left); register_action(view, cx, Editor::move_right); register_action(view, cx, Editor::move_down); diff --git a/crates/editor2/src/inlay_hint_cache.rs b/crates/editor2/src/inlay_hint_cache.rs index aab985ff9030988481796b0a4181189662f749c9..32c4f5b61fda0a65c4798c20387c9e892165cbfa 100644 --- a/crates/editor2/src/inlay_hint_cache.rs +++ b/crates/editor2/src/inlay_hint_cache.rs @@ -1202,6 +1202,7 @@ 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; @@ -1214,6 +1215,7 @@ 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; diff --git a/crates/editor2/src/movement.rs b/crates/editor2/src/movement.rs index ab25bb8499aa323178d3573ad563797f1ec0a712..0f92f37fd8359f80411c11a5fdbaeef9096ec550 100644 --- a/crates/editor2/src/movement.rs +++ b/crates/editor2/src/movement.rs @@ -460,10 +460,11 @@ 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::post_inc; + use util::{http::FakeHttpClient, post_inc}; #[gpui::test] fn test_previous_word_start(cx: &mut gpui::AppContext) { diff --git a/crates/editor2/src/rust_analyzer_ext.rs b/crates/editor2/src/rust_analyzer_ext.rs new file mode 100644 index 0000000000000000000000000000000000000000..e484136e49ab63bd10e42c01b30ef5b2629fe49b --- /dev/null +++ b/crates/editor2/src/rust_analyzer_ext.rs @@ -0,0 +1,75 @@ +use std::{path::Path, 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 crate::{element::register_action, Editor, ExpandMacroRecursively}; + +pub fn apply_related_actions(editor: &View, cx: &mut WindowContext) { + let is_rust_related = editor.update(cx, |editor, cx| { + editor + .buffer() + .read(cx) + .all_buffers() + .iter() + .any(|b| b.read(cx).language().map(|l| l.name()).as_deref() == Some("Rust")) + }); + + if is_rust_related { + register_action(editor, cx, expand_macro_recursively); + } +} + +pub fn expand_macro_recursively( + editor: &mut Editor, + _: &ExpandMacroRecursively, + cx: &mut ViewContext<'_, Editor>, +) { + if editor.selections.count() == 0 { + return; + } + let Some(project) = &editor.project else { + return; + }; + + let multibuffer = editor.buffer().read(cx); + + let Some((trigger_anchor, 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)| { + let buffer = multibuffer.buffer(buffer_id)?; + 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())) + } else { + None + } + }) + }) + else { + return; + }; + + let z = project.update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + ExpandMacro {}, + cx, + ) + }); + + // todo!("TODO kb") +} diff --git a/crates/editor2/src/test/editor_lsp_test_context.rs b/crates/editor2/src/test/editor_lsp_test_context.rs index 7ee55cddba1edba9356be2c6773c3f097f57c1c8..53540fb505f493d0e1ece158369de37d0f4a69fb 100644 --- a/crates/editor2/src/test/editor_lsp_test_context.rs +++ b/crates/editor2/src/test/editor_lsp_test_context.rs @@ -5,7 +5,9 @@ use std::{ }; use anyhow::Result; +use client::Client; use serde_json::json; +use util::http::FakeHttpClient; use crate::{Editor, ToPoint}; use collections::HashSet; diff --git a/crates/project2/src/lsp_command.rs b/crates/project2/src/lsp_command.rs index a2de52b21ae277db5d15863e2adc283bd08c7b78..52836f4c0030e005eb19d396c77bfb49fc0ea604 100644 --- a/crates/project2/src/lsp_command.rs +++ b/crates/project2/src/lsp_command.rs @@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { } #[async_trait(?Send)] -pub(crate) trait LspCommand: 'static + Sized + Send { +pub trait LspCommand: 'static + Sized + Send { type Response: 'static + Default + Send; type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; diff --git a/crates/project2/src/lsp_ext_command.rs b/crates/project2/src/lsp_ext_command.rs new file mode 100644 index 0000000000000000000000000000000000000000..63a782df512dacc50dc248c9d49575dae8cc6767 --- /dev/null +++ b/crates/project2/src/lsp_ext_command.rs @@ -0,0 +1,100 @@ +use std::{path::Path, sync::Arc}; + +use async_trait::async_trait; +use gpui::{AppContext, AsyncAppContext, Model}; +use language::Buffer; +use lsp::{LanguageServer, LanguageServerId}; +use rpc::proto::{self, PeerId}; +use serde::{Deserialize, Serialize}; + +use crate::{lsp_command::LspCommand, Project}; + +pub enum LspExpandMacro {} + +impl lsp::request::Request for LspExpandMacro { + type Params = ExpandMacroParams; + type Result = Option; + const METHOD: &'static str = "rust-analyzer/expandMacro"; +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ExpandMacroParams { + pub text_document: lsp::TextDocumentIdentifier, + pub position: lsp::Position, +} + +#[derive(Default, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ExpandedMacro { + pub name: String, + pub expansion: String, +} + +pub struct ExpandMacro {} + +// TODO kb +#[async_trait(?Send)] +impl LspCommand for ExpandMacro { + type Response = ExpandedMacro; + type LspRequest = LspExpandMacro; + type ProtoRequest = proto::LspExtExpandMacro; + + fn to_lsp( + &self, + path: &Path, + buffer: &Buffer, + language_server: &Arc, + cx: &AppContext, + ) -> ExpandMacroParams { + todo!() + } + + async fn response_from_lsp( + self, + message: Option, + project: Model, + buffer: Model, + server_id: LanguageServerId, + cx: AsyncAppContext, + ) -> anyhow::Result { + anyhow::bail!("TODO kb") + } + + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtExpandMacro { + todo!() + } + + async fn from_proto( + message: Self::ProtoRequest, + project: Model, + buffer: Model, + cx: AsyncAppContext, + ) -> anyhow::Result { + todo!() + } + + fn response_to_proto( + response: ExpandedMacro, + project: &mut Project, + peer_id: PeerId, + buffer_version: &clock::Global, + cx: &mut AppContext, + ) -> proto::LspExtExpandMacroResponse { + todo!() + } + + async fn response_from_proto( + self, + message: proto::LspExtExpandMacroResponse, + project: Model, + buffer: Model, + cx: AsyncAppContext, + ) -> anyhow::Result { + todo!() + } + + fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 { + message.buffer_id + } +} diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 243f896b0f14035e399f382b4b68e2f16ffd7438..fe3498b930ef76ad7f061aed7af19c5c66aa96a0 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -1,5 +1,6 @@ mod ignore; -mod lsp_command; +pub mod lsp_command; +pub mod lsp_ext_command; mod prettier_support; pub mod project_settings; pub mod search; @@ -172,7 +173,7 @@ struct DelayedDebounced { cancel_channel: Option>, } -enum LanguageServerToQuery { +pub enum LanguageServerToQuery { Primary, Other(LanguageServerId), } @@ -623,6 +624,7 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); + client.add_model_request_handler(Self::handle_lsp_command::); } pub fn local( @@ -5933,7 +5935,7 @@ impl Project { .await; } - fn request_lsp( + pub fn request_lsp( &self, buffer_handle: Model, server: LanguageServerToQuery, diff --git a/crates/rpc2/proto/zed.proto b/crates/rpc2/proto/zed.proto index 611514aacb44f9445674f2dca0b947eda8088ee3..0ddeb053779a84ddbbc4283c6a9d2b42e7bbe637 100644 --- a/crates/rpc2/proto/zed.proto +++ b/crates/rpc2/proto/zed.proto @@ -178,7 +178,9 @@ message Envelope { GetNotifications get_notifications = 150; GetNotificationsResponse get_notifications_response = 151; DeleteNotification delete_notification = 152; - MarkNotificationRead mark_notification_read = 153; // Current max + MarkNotificationRead mark_notification_read = 153; + LspExtExpandMacro lsp_ext_expand_macro = 154; + LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max } } @@ -1619,3 +1621,11 @@ message Notification { bool is_read = 6; optional bool response = 7; } + +message LspExtExpandMacro { + uint64 project_id = 1; + uint64 buffer_id = 2; +} + +message LspExtExpandMacroResponse { +} diff --git a/crates/rpc2/src/proto.rs b/crates/rpc2/src/proto.rs index 77a69122c20ce29fd03c647061d2c150b59961e7..336c252630ea51ea9d11a0658fc59d0cb09b214f 100644 --- a/crates/rpc2/src/proto.rs +++ b/crates/rpc2/src/proto.rs @@ -280,6 +280,8 @@ messages!( (UpdateWorktree, Foreground), (UpdateWorktreeSettings, Foreground), (UsersResponse, Foreground), + (LspExtExpandMacro, Background), + (LspExtExpandMacroResponse, Background), ); request_messages!( @@ -363,6 +365,7 @@ request_messages!( (UpdateParticipantLocation, Ack), (UpdateProject, Ack), (UpdateWorktree, Ack), + (LspExtExpandMacro, LspExtExpandMacroResponse), ); entity_messages!( @@ -415,6 +418,7 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, + LspExtExpandMacro, ); entity_messages!( From 3694265b6b34b933fab394c654c7d9fcca063e74 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 11 Dec 2023 12:01:04 +0200 Subject: [PATCH 2/3] Finalize the command --- 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 ++++++++++++--- .../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(-) diff --git a/crates/editor2/src/display_map.rs b/crates/editor2/src/display_map.rs index dde063dab22c36d2b3da531681a2f80dd8e54bc6..60975a7a5caec6552b3154e528b9629e9beb094c 100644 --- a/crates/editor2/src/display_map.rs +++ b/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)] diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index 907a2e21535962360d46bed5b9dd9cfaec22b44c..54624a694566ad5efc7fbf8444886ea48e5e15fe 100644 --- a/crates/editor2/src/editor.rs +++ b/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), diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 193ea5bc4b7eef3ec2d294109102a9d93111293e..2548fc40a4afc2262a533536a6230c650183b1f0 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/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::{ diff --git a/crates/editor2/src/inlay_hint_cache.rs b/crates/editor2/src/inlay_hint_cache.rs index 32c4f5b61fda0a65c4798c20387c9e892165cbfa..aab985ff9030988481796b0a4181189662f749c9 100644 --- a/crates/editor2/src/inlay_hint_cache.rs +++ b/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; diff --git a/crates/editor2/src/movement.rs b/crates/editor2/src/movement.rs index 0f92f37fd8359f80411c11a5fdbaeef9096ec550..ab25bb8499aa323178d3573ad563797f1ec0a712 100644 --- a/crates/editor2/src/movement.rs +++ b/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) { diff --git a/crates/editor2/src/rust_analyzer_ext.rs b/crates/editor2/src/rust_analyzer_ext.rs index e484136e49ab63bd10e42c01b30ef5b2629fe49b..0ebf242504098451e34a4d6b7163940055815431 100644 --- a/crates/editor2/src/rust_analyzer_ext.rs +++ b/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, 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(¯o_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" } diff --git a/crates/editor2/src/test/editor_lsp_test_context.rs b/crates/editor2/src/test/editor_lsp_test_context.rs index 53540fb505f493d0e1ece158369de37d0f4a69fb..7ee55cddba1edba9356be2c6773c3f097f57c1c8 100644 --- a/crates/editor2/src/test/editor_lsp_test_context.rs +++ b/crates/editor2/src/test/editor_lsp_test_context.rs @@ -5,9 +5,7 @@ use std::{ }; use anyhow::Result; -use client::Client; use serde_json::json; -use util::http::FakeHttpClient; use crate::{Editor, ToPoint}; use collections::HashSet; diff --git a/crates/project2/src/lsp_ext_command.rs b/crates/project2/src/lsp_ext_command.rs index 63a782df512dacc50dc248c9d49575dae8cc6767..683e5087ccbc2c665f7c5556785befc181b3491a 100644 --- a/crates/project2/src/lsp_ext_command.rs +++ b/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, - cx: &AppContext, + _: &Buffer, + _: &Arc, + _: &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, - project: Model, - buffer: Model, - server_id: LanguageServerId, - cx: AsyncAppContext, + _: Model, + _: Model, + _: LanguageServerId, + _: AsyncAppContext, ) -> anyhow::Result { - 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, + _: Model, buffer: Model, - cx: AsyncAppContext, + mut cx: AsyncAppContext, ) -> anyhow::Result { - 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, - buffer: Model, - cx: AsyncAppContext, + _: Model, + _: Model, + _: AsyncAppContext, ) -> anyhow::Result { - todo!() + Ok(ExpandedMacro { + name: message.name, + expansion: message.expansion, + }) } fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 { diff --git a/crates/rpc2/proto/zed.proto b/crates/rpc2/proto/zed.proto index 0ddeb053779a84ddbbc4283c6a9d2b42e7bbe637..5ae127e2775af712895353d76e9a5c474f02b509 100644 --- a/crates/rpc2/proto/zed.proto +++ b/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; } From 55374e8ac08d6e29c2fe6d518cf8fe4c97fbd322 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 11 Dec 2023 12:28:14 +0200 Subject: [PATCH 3/3] Port to gpui1 --- crates/editor/src/editor.rs | 4 + crates/editor/src/rust_analyzer_ext.rs | 98 ++++++++++++++++++ crates/project/src/lsp_command.rs | 2 +- crates/project/src/lsp_ext_command.rs | 137 +++++++++++++++++++++++++ crates/project/src/project.rs | 8 +- crates/rpc/proto/zed.proto | 15 ++- crates/rpc/src/proto.rs | 4 + 7 files changed, 263 insertions(+), 5 deletions(-) create mode 100644 crates/editor/src/rust_analyzer_ext.rs create mode 100644 crates/project/src/lsp_ext_command.rs diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 76dec3e1b6e3c41a74480020e9c35ce32968704c..5cfcf02174e0dc4d6494b246409671c96c6f3cf9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -12,6 +12,7 @@ mod link_go_to_definition; mod mouse_context_menu; pub mod movement; mod persistence; +mod rust_analyzer_ext; pub mod scroll; pub mod selections_collection; @@ -300,6 +301,7 @@ actions!( DeleteToEndOfLine, CutToEndOfLine, DuplicateLine, + ExpandMacroRecursively, MoveLineUp, MoveLineDown, JoinLines, @@ -425,6 +427,8 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) { init_settings(cx); + + rust_analyzer_ext::apply_related_actions(cx); cx.add_action(Editor::new_file); cx.add_action(Editor::new_file_in_direction); cx.add_action(Editor::cancel); diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs new file mode 100644 index 0000000000000000000000000000000000000000..197215ddaa0105418f2f088935e96cd92928c375 --- /dev/null +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -0,0 +1,98 @@ +use std::sync::Arc; + +use anyhow::Context as _; +use gpui::{AppContext, Task, ViewContext}; +use language::Language; +use multi_buffer::MultiBuffer; +use project::lsp_ext_command::ExpandMacro; +use text::ToPointUtf16; + +use crate::{Editor, ExpandMacroRecursively}; + +pub fn apply_related_actions(cx: &mut AppContext) { + cx.add_async_action(expand_macro_recursively); +} + +pub fn expand_macro_recursively( + editor: &mut Editor, + _: &ExpandMacroRecursively, + cx: &mut ViewContext<'_, '_, Editor>, +) -> Option>> { + if editor.selections.count() == 0 { + return None; + } + let project = editor.project.as_ref()?; + let workspace = editor.workspace(cx)?; + let multibuffer = editor.buffer().read(cx); + + let (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))) + .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, + Arc::clone(&rust_language), + server.server_id(), + buffer.clone(), + )) + } else { + None + } + }) + })?; + + 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 { position }, + cx, + ) + }); + Some(cx.spawn(|_, 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(¯o_expansion.expansion, Some(rust_language), cx) + })?; + workspace.update(&mut cx, |workspace, cx| { + let buffer = cx.add_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name) + }); + workspace.add_item( + Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))), + cx, + ); + }); + + anyhow::Ok(()) + })) +} + +fn is_rust_language(language: &Language) -> bool { + language.name().as_ref() == "Rust" +} diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 72d79ca97992c47384f004c6d1f25bb7f59f22a1..8d40f6dcb2bfe41be23d079b073984dc5cd4d93a 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -33,7 +33,7 @@ pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions { } #[async_trait(?Send)] -pub(crate) trait LspCommand: 'static + Sized { +pub trait LspCommand: 'static + Sized { type Response: 'static + Default + Send; type LspRequest: 'static + Send + lsp::request::Request; type ProtoRequest: 'static + Send + proto::RequestMessage; diff --git a/crates/project/src/lsp_ext_command.rs b/crates/project/src/lsp_ext_command.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d6bd0f967cb5c460d3708a6696520e7252a7b38 --- /dev/null +++ b/crates/project/src/lsp_ext_command.rs @@ -0,0 +1,137 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Context; +use async_trait::async_trait; +use gpui::{AppContext, AsyncAppContext, ModelHandle}; +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}; + +pub enum LspExpandMacro {} + +impl lsp::request::Request for LspExpandMacro { + type Params = ExpandMacroParams; + type Result = Option; + const METHOD: &'static str = "rust-analyzer/expandMacro"; +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ExpandMacroParams { + pub text_document: lsp::TextDocumentIdentifier, + pub position: lsp::Position, +} + +#[derive(Default, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ExpandedMacro { + pub name: String, + pub expansion: String, +} + +impl ExpandedMacro { + pub fn is_empty(&self) -> bool { + self.name.is_empty() && self.expansion.is_empty() + } +} + +pub struct ExpandMacro { + pub position: PointUtf16, +} + +#[async_trait(?Send)] +impl LspCommand for ExpandMacro { + type Response = ExpandedMacro; + type LspRequest = LspExpandMacro; + type ProtoRequest = proto::LspExtExpandMacro; + + fn to_lsp( + &self, + path: &Path, + _: &Buffer, + _: &Arc, + _: &AppContext, + ) -> ExpandMacroParams { + 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, + _: ModelHandle, + _: ModelHandle, + _: LanguageServerId, + _: AsyncAppContext, + ) -> anyhow::Result { + 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 { + 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, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + 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, + _: &mut Project, + _: PeerId, + _: &clock::Global, + _: &mut AppContext, + ) -> proto::LspExtExpandMacroResponse { + proto::LspExtExpandMacroResponse { + name: response.name, + expansion: response.expansion, + } + } + + async fn response_from_proto( + self, + message: proto::LspExtExpandMacroResponse, + _: ModelHandle, + _: ModelHandle, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(ExpandedMacro { + name: message.name, + expansion: message.expansion, + }) + } + + fn buffer_id_from_proto(message: &proto::LspExtExpandMacro) -> u64 { + message.buffer_id + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2e779b71b2c4c2765c2c73745ac6ebd24db44bc9..8b059948c8c5bc45628585c1da038843faacf55c 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1,5 +1,6 @@ mod ignore; -mod lsp_command; +pub mod lsp_command; +pub mod lsp_ext_command; mod prettier_support; pub mod project_settings; pub mod search; @@ -174,7 +175,7 @@ struct DelayedDebounced { cancel_channel: Option>, } -enum LanguageServerToQuery { +pub enum LanguageServerToQuery { Primary, Other(LanguageServerId), } @@ -626,6 +627,7 @@ impl Project { client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_save_buffer); client.add_model_message_handler(Self::handle_update_diff_base); + client.add_model_request_handler(Self::handle_lsp_command::); } pub fn local( @@ -5863,7 +5865,7 @@ impl Project { .await; } - fn request_lsp( + pub fn request_lsp( &self, buffer_handle: ModelHandle, server: LanguageServerToQuery, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 611514aacb44f9445674f2dca0b947eda8088ee3..5ae127e2775af712895353d76e9a5c474f02b509 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -178,7 +178,9 @@ message Envelope { GetNotifications get_notifications = 150; GetNotificationsResponse get_notifications_response = 151; DeleteNotification delete_notification = 152; - MarkNotificationRead mark_notification_read = 153; // Current max + MarkNotificationRead mark_notification_read = 153; + LspExtExpandMacro lsp_ext_expand_macro = 154; + LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; // Current max } } @@ -1619,3 +1621,14 @@ message Notification { bool is_read = 6; optional bool response = 7; } + +message LspExtExpandMacro { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor position = 3; +} + +message LspExtExpandMacroResponse { + string name = 1; + string expansion = 2; +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 77a69122c20ce29fd03c647061d2c150b59961e7..336c252630ea51ea9d11a0658fc59d0cb09b214f 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -280,6 +280,8 @@ messages!( (UpdateWorktree, Foreground), (UpdateWorktreeSettings, Foreground), (UsersResponse, Foreground), + (LspExtExpandMacro, Background), + (LspExtExpandMacroResponse, Background), ); request_messages!( @@ -363,6 +365,7 @@ request_messages!( (UpdateParticipantLocation, Ack), (UpdateProject, Ack), (UpdateWorktree, Ack), + (LspExtExpandMacro, LspExtExpandMacroResponse), ); entity_messages!( @@ -415,6 +418,7 @@ entity_messages!( UpdateProjectCollaborator, UpdateWorktree, UpdateWorktreeSettings, + LspExtExpandMacro, ); entity_messages!(