diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 3813ee67fd..3874500d25 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -327,6 +327,7 @@ impl Server { .add_request_handler( forward_read_only_project_request::, ) + .add_request_handler(forward_read_only_project_request::) .add_request_handler( forward_read_only_project_request::, ) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index d1f712b62e..4258d0020c 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -308,6 +308,7 @@ actions!( GoToImplementation, GoToImplementationSplit, GoToNextChange, + GoToParentModule, GoToPreviousChange, GoToPreviousDiagnostic, GoToTypeDefinition, diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 412a59b840..80d811f8a3 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -4,15 +4,19 @@ use anyhow::Context as _; use gpui::{App, AppContext as _, Context, Entity, Window}; use language::{Capability, Language, proto::serialize_anchor}; use multi_buffer::MultiBuffer; -use project::lsp_store::{ - lsp_ext_command::{DocsUrls, ExpandMacro, ExpandedMacro}, - rust_analyzer_ext::RUST_ANALYZER_NAME, +use project::{ + lsp_command::location_link_from_proto, + lsp_store::{ + lsp_ext_command::{DocsUrls, ExpandMacro, ExpandedMacro}, + rust_analyzer_ext::RUST_ANALYZER_NAME, + }, }; use rpc::proto; use text::ToPointUtf16; use crate::{ - Editor, ExpandMacroRecursively, OpenDocs, element::register_action, + Editor, ExpandMacroRecursively, GoToParentModule, GotoDefinitionKind, OpenDocs, + element::register_action, hover_links::HoverLink, lsp_ext::find_specific_language_server_in_selection, }; @@ -30,11 +34,94 @@ pub fn apply_related_actions(editor: &Entity, window: &mut Window, cx: & .filter_map(|buffer| buffer.read(cx).language()) .any(|language| is_rust_language(language)) { + register_action(&editor, window, go_to_parent_module); register_action(&editor, window, expand_macro_recursively); register_action(&editor, window, open_docs); } } +pub fn go_to_parent_module( + editor: &mut Editor, + _: &GoToParentModule, + window: &mut Window, + cx: &mut Context, +) { + if editor.selections.count() == 0 { + return; + } + let Some(project) = &editor.project else { + return; + }; + + let server_lookup = find_specific_language_server_in_selection( + editor, + cx, + is_rust_language, + RUST_ANALYZER_NAME, + ); + + let project = project.clone(); + let lsp_store = project.read(cx).lsp_store(); + let upstream_client = lsp_store.read(cx).upstream_client(); + cx.spawn_in(window, async move |editor, cx| { + let Some((trigger_anchor, _, server_to_query, buffer)) = server_lookup.await else { + return anyhow::Ok(()); + }; + + let location_links = if let Some((client, project_id)) = upstream_client { + let buffer_id = buffer.update(cx, |buffer, _| buffer.remote_id())?; + + let request = proto::LspExtGoToParentModule { + project_id, + buffer_id: buffer_id.to_proto(), + position: Some(serialize_anchor(&trigger_anchor.text_anchor)), + }; + let response = client + .request(request) + .await + .context("lsp ext go to parent module proto request")?; + futures::future::join_all( + response + .links + .into_iter() + .map(|link| location_link_from_proto(link, lsp_store.clone(), cx)), + ) + .await + .into_iter() + .collect::>() + .context("go to parent module via collab")? + } else { + let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?; + let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); + project + .update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + project::lsp_store::lsp_ext_command::GoToParentModule { position }, + cx, + ) + })? + .await + .context("go to parent module")? + }; + + editor + .update_in(cx, |editor, window, cx| { + editor.navigate_to_hover_links( + Some(GotoDefinitionKind::Declaration), + location_links.into_iter().map(HoverLink::Text).collect(), + false, + window, + cx, + ) + })? + .await?; + Ok(()) + }) + .detach_and_log_err(cx); +} + pub fn expand_macro_recursively( editor: &mut Editor, _: &ExpandMacroRecursively, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 20111ae688..05f33de6ce 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -13,7 +13,7 @@ use client::proto::{self, PeerId}; use clock::Global; use collections::HashSet; use futures::future; -use gpui::{App, AsyncApp, Entity}; +use gpui::{App, AsyncApp, Entity, Task}; use language::{ Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, @@ -966,7 +966,7 @@ fn language_server_for_buffer( .ok_or_else(|| anyhow!("no language server found for buffer")) } -async fn location_links_from_proto( +pub async fn location_links_from_proto( proto_links: Vec, lsp_store: Entity, mut cx: AsyncApp, @@ -974,70 +974,72 @@ async fn location_links_from_proto( let mut links = Vec::new(); for link in proto_links { - links.push(location_link_from_proto(link, &lsp_store, &mut cx).await?) + links.push(location_link_from_proto(link, lsp_store.clone(), &mut cx).await?) } Ok(links) } -pub async fn location_link_from_proto( +pub fn location_link_from_proto( link: proto::LocationLink, - lsp_store: &Entity, + lsp_store: Entity, cx: &mut AsyncApp, -) -> Result { - let origin = match link.origin { - Some(origin) => { - let buffer_id = BufferId::new(origin.buffer_id)?; - let buffer = lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.wait_for_remote_buffer(buffer_id, cx) - })? - .await?; - let start = origin - .start - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing origin start"))?; - let end = origin - .end - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing origin end"))?; - buffer - .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))? - .await?; - Some(Location { - buffer, - range: start..end, - }) - } - None => None, - }; +) -> Task> { + cx.spawn(async move |cx| { + let origin = match link.origin { + Some(origin) => { + let buffer_id = BufferId::new(origin.buffer_id)?; + let buffer = lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.wait_for_remote_buffer(buffer_id, cx) + })? + .await?; + let start = origin + .start + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing origin start"))?; + let end = origin + .end + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing origin end"))?; + buffer + .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))? + .await?; + Some(Location { + buffer, + range: start..end, + }) + } + None => None, + }; - let target = link.target.ok_or_else(|| anyhow!("missing target"))?; - let buffer_id = BufferId::new(target.buffer_id)?; - let buffer = lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.wait_for_remote_buffer(buffer_id, cx) - })? - .await?; - let start = target - .start - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing target start"))?; - let end = target - .end - .and_then(deserialize_anchor) - .ok_or_else(|| anyhow!("missing target end"))?; - buffer - .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))? - .await?; - let target = Location { - buffer, - range: start..end, - }; - Ok(LocationLink { origin, target }) + let target = link.target.ok_or_else(|| anyhow!("missing target"))?; + let buffer_id = BufferId::new(target.buffer_id)?; + let buffer = lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.wait_for_remote_buffer(buffer_id, cx) + })? + .await?; + let start = target + .start + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing target start"))?; + let end = target + .end + .and_then(deserialize_anchor) + .ok_or_else(|| anyhow!("missing target end"))?; + buffer + .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))? + .await?; + let target = Location { + buffer, + range: start..end, + }; + Ok(LocationLink { origin, target }) + }) } -async fn location_links_from_lsp( +pub async fn location_links_from_lsp( message: Option, lsp_store: Entity, buffer: Entity, @@ -1178,7 +1180,7 @@ pub async fn location_link_from_lsp( }) } -fn location_links_to_proto( +pub fn location_links_to_proto( links: Vec, lsp_store: &mut LspStore, peer_id: PeerId, diff --git a/crates/project/src/lsp_store/lsp_ext_command.rs b/crates/project/src/lsp_store/lsp_ext_command.rs index 91075cd233..a7e6eafc74 100644 --- a/crates/project/src/lsp_store/lsp_ext_command.rs +++ b/crates/project/src/lsp_store/lsp_ext_command.rs @@ -2,9 +2,10 @@ use crate::{ LocationLink, lsp_command::{ LspCommand, location_link_from_lsp, location_link_from_proto, location_link_to_proto, + location_links_from_lsp, location_links_from_proto, location_links_to_proto, }, lsp_store::LspStore, - make_text_document_identifier, + make_lsp_text_document_position, make_text_document_identifier, }; use anyhow::{Context as _, Result}; use async_trait::async_trait; @@ -301,6 +302,19 @@ pub struct SwitchSourceHeaderResult(pub String); #[serde(rename_all = "camelCase")] pub struct SwitchSourceHeader; +#[derive(Debug)] +pub struct GoToParentModule { + pub position: PointUtf16, +} + +pub struct LspGoToParentModule {} + +impl lsp::request::Request for LspGoToParentModule { + type Params = lsp::TextDocumentPositionParams; + type Result = Option>; + const METHOD: &'static str = "experimental/parentModule"; +} + #[async_trait(?Send)] impl LspCommand for SwitchSourceHeader { type Response = SwitchSourceHeaderResult; @@ -379,6 +393,96 @@ impl LspCommand for SwitchSourceHeader { } } +#[async_trait(?Send)] +impl LspCommand for GoToParentModule { + type Response = Vec; + type LspRequest = LspGoToParentModule; + type ProtoRequest = proto::LspExtGoToParentModule; + + fn display_name(&self) -> &str { + "Go to parent module" + } + + fn to_lsp( + &self, + path: &Path, + _: &Buffer, + _: &Arc, + _: &App, + ) -> Result { + make_lsp_text_document_position(path, self.position) + } + + async fn response_from_lsp( + self, + links: Option>, + lsp_store: Entity, + buffer: Entity, + server_id: LanguageServerId, + cx: AsyncApp, + ) -> anyhow::Result> { + location_links_from_lsp( + links.map(lsp::GotoDefinitionResponse::Link), + lsp_store, + buffer, + server_id, + cx, + ) + .await + } + + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtGoToParentModule { + proto::LspExtGoToParentModule { + project_id, + buffer_id: buffer.remote_id().to_proto(), + position: Some(language::proto::serialize_anchor( + &buffer.anchor_before(self.position), + )), + } + } + + async fn from_proto( + request: Self::ProtoRequest, + _: Entity, + buffer: Entity, + mut cx: AsyncApp, + ) -> anyhow::Result { + let position = request + .position + .and_then(deserialize_anchor) + .context("bad request with bad position")?; + Ok(Self { + position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + }) + } + + fn response_to_proto( + links: Vec, + lsp_store: &mut LspStore, + peer_id: PeerId, + _: &clock::Global, + cx: &mut App, + ) -> proto::LspExtGoToParentModuleResponse { + proto::LspExtGoToParentModuleResponse { + links: location_links_to_proto(links, lsp_store, peer_id, cx), + } + } + + async fn response_from_proto( + self, + message: proto::LspExtGoToParentModuleResponse, + lsp_store: Entity, + _: Entity, + cx: AsyncApp, + ) -> anyhow::Result> { + location_links_from_proto(message.links, lsp_store, cx).await + } + + fn buffer_id_from_proto(message: &proto::LspExtGoToParentModule) -> Result { + BufferId::new(message.buffer_id) + } +} + // https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#runnables // Taken from https://github.com/rust-lang/rust-analyzer/blob/a73a37a757a58b43a796d3eb86a1f7dfd0036659/crates/rust-analyzer/src/lsp/ext.rs#L425-L489 pub enum Runnables {} @@ -633,7 +737,7 @@ impl LspCommand for GetLspRunnables { for lsp_runnable in message.runnables { let location = match lsp_runnable.location { Some(location) => { - Some(location_link_from_proto(location, &lsp_store, &mut cx).await?) + Some(location_link_from_proto(location, lsp_store.clone(), &mut cx).await?) } None => None, }; diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index 38bd18d49c..906402b089 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -182,6 +182,16 @@ message LspExtSwitchSourceHeaderResponse { string target_file = 1; } +message LspExtGoToParentModule { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor position = 3; +} + +message LspExtGoToParentModuleResponse { + repeated LocationLink links = 1; +} + message GetCompletionsResponse { repeated Completion completions = 1; repeated VectorClockEntry version = 2; diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index ebb6623c3f..101db00969 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -378,7 +378,10 @@ message Envelope { GetDebugAdapterBinary get_debug_adapter_binary = 339; DebugAdapterBinary debug_adapter_binary = 340; RunDebugLocators run_debug_locators = 341; - DebugRequest debug_request = 342; // current max + DebugRequest debug_request = 342; + + LspExtGoToParentModule lsp_ext_go_to_parent_module = 343; + LspExtGoToParentModuleResponse lsp_ext_go_to_parent_module_response = 344;// current max } reserved 87 to 88; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 61c2b66ebe..24963fb97b 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -169,6 +169,8 @@ messages!( (LspExtRunnablesResponse, Background), (LspExtSwitchSourceHeader, Background), (LspExtSwitchSourceHeaderResponse, Background), + (LspExtGoToParentModule, Background), + (LspExtGoToParentModuleResponse, Background), (MarkNotificationRead, Foreground), (MoveChannel, Foreground), (MultiLspQuery, Background), @@ -422,6 +424,7 @@ request_messages!( (CreateContext, CreateContextResponse), (SynchronizeContexts, SynchronizeContextsResponse), (LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse), + (LspExtGoToParentModule, LspExtGoToParentModuleResponse), (AddWorktree, AddWorktreeResponse), (ShutdownRemoteServer, Ack), (RemoveWorktree, Ack), @@ -544,6 +547,7 @@ entity_messages!( UpdateContext, SynchronizeContexts, LspExtSwitchSourceHeader, + LspExtGoToParentModule, LanguageServerLog, Toast, HideToast,