Detailed changes
@@ -327,6 +327,7 @@ impl Server {
.add_request_handler(
forward_read_only_project_request::<proto::LspExtSwitchSourceHeader>,
)
+ .add_request_handler(forward_read_only_project_request::<proto::LspExtGoToParentModule>)
.add_request_handler(
forward_read_only_project_request::<proto::LanguageServerIdForName>,
)
@@ -308,6 +308,7 @@ actions!(
GoToImplementation,
GoToImplementationSplit,
GoToNextChange,
+ GoToParentModule,
GoToPreviousChange,
GoToPreviousDiagnostic,
GoToTypeDefinition,
@@ -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<Editor>, 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<Editor>,
+) {
+ 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::<anyhow::Result<_>>()
+ .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,
@@ -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<proto::LocationLink>,
lsp_store: Entity<LspStore>,
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<LspStore>,
+ lsp_store: Entity<LspStore>,
cx: &mut AsyncApp,
-) -> Result<LocationLink> {
- 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<Result<LocationLink>> {
+ 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::GotoDefinitionResponse>,
lsp_store: Entity<LspStore>,
buffer: Entity<Buffer>,
@@ -1178,7 +1180,7 @@ pub async fn location_link_from_lsp(
})
}
-fn location_links_to_proto(
+pub fn location_links_to_proto(
links: Vec<LocationLink>,
lsp_store: &mut LspStore,
peer_id: PeerId,
@@ -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<Vec<lsp::LocationLink>>;
+ 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<LocationLink>;
+ type LspRequest = LspGoToParentModule;
+ type ProtoRequest = proto::LspExtGoToParentModule;
+
+ fn display_name(&self) -> &str {
+ "Go to parent module"
+ }
+
+ fn to_lsp(
+ &self,
+ path: &Path,
+ _: &Buffer,
+ _: &Arc<LanguageServer>,
+ _: &App,
+ ) -> Result<lsp::TextDocumentPositionParams> {
+ make_lsp_text_document_position(path, self.position)
+ }
+
+ async fn response_from_lsp(
+ self,
+ links: Option<Vec<lsp::LocationLink>>,
+ lsp_store: Entity<LspStore>,
+ buffer: Entity<Buffer>,
+ server_id: LanguageServerId,
+ cx: AsyncApp,
+ ) -> anyhow::Result<Vec<LocationLink>> {
+ 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<LspStore>,
+ buffer: Entity<Buffer>,
+ mut cx: AsyncApp,
+ ) -> anyhow::Result<Self> {
+ 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<LocationLink>,
+ 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<LspStore>,
+ _: Entity<Buffer>,
+ cx: AsyncApp,
+ ) -> anyhow::Result<Vec<LocationLink>> {
+ location_links_from_proto(message.links, lsp_store, cx).await
+ }
+
+ fn buffer_id_from_proto(message: &proto::LspExtGoToParentModule) -> Result<BufferId> {
+ 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,
};
@@ -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;
@@ -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;
@@ -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,