From 9aad30a559b53dc138726caf3f5a1c51afcc4d9f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Wed, 3 Apr 2024 12:34:56 +0200 Subject: [PATCH] Query code actions and hovers from all related local language servers (from remote clients) (#10111) Supersedes https://github.com/zed-industries/zed/pull/8634 Fixes https://github.com/zed-industries/zed/issues/7947 by continuing https://github.com/zed-industries/zed/pull/9943 with the remote part. Now, clients are able to issue collab requests, that query all related language servers, not only the primary one. Such mode is enabled for GetHover and GetCodeActions LSP requests only. Release Notes: - Added Tailwind CSS hover popovers for Zed in multi player mode ([7947](https://github.com/zed-industries/zed/issues/7947)) --- crates/collab/src/rpc.rs | 1 + crates/collab/src/tests/integration_tests.rs | 197 ++++++--- crates/project/src/lsp_command.rs | 2 + crates/project/src/project.rs | 408 ++++++++++++++----- crates/project/src/project_tests.rs | 20 +- crates/rpc/proto/zed.proto | 33 +- crates/rpc/src/proto.rs | 4 + 7 files changed, 501 insertions(+), 164 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 16f131af5162b8a1ee3685fe5eaf26717a43188e..a2a4c16a2a00934353da2bada182470798005759 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -368,6 +368,7 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) .add_request_handler(forward_mutating_project_request::) + .add_request_handler(forward_mutating_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) .add_message_handler(broadcast_project_message_from_host::) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 598d755e9262940c2d5b838ee6afb30940b9b0c9..b15e2b5123d627f1c773f523f0a973b0216ee19e 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -4934,9 +4934,35 @@ async fn test_lsp_hover( .await; client_a.language_registry().add(rust_lang()); + let language_server_names = ["rust-analyzer", "CrabLang-ls"]; let mut fake_language_servers = client_a .language_registry() - .register_fake_lsp_adapter("Rust", Default::default()); + .register_specific_fake_lsp_adapter( + "Rust", + true, + FakeLspAdapter { + name: "rust-analyzer", + capabilities: lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() + }, + ); + let _other_server = client_a + .language_registry() + .register_specific_fake_lsp_adapter( + "Rust", + false, + FakeLspAdapter { + name: "CrabLang-ls", + capabilities: lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() + }, + ); let (project_a, worktree_id) = client_a.build_local_project("/root-1", cx_a).await; let project_id = active_call_a @@ -4949,66 +4975,133 @@ async fn test_lsp_hover( let open_buffer = project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)); let buffer_b = cx_b.executor().spawn(open_buffer).await.unwrap(); - // Request hover information as the guest. - let fake_language_server = fake_language_servers.next().await.unwrap(); - fake_language_server.handle_request::( - |params, _| async move { - assert_eq!( - params - .text_document_position_params - .text_document - .uri - .as_str(), - "file:///root-1/main.rs" - ); - assert_eq!( - params.text_document_position_params.position, - lsp::Position::new(0, 22) - ); - Ok(Some(lsp::Hover { - contents: lsp::HoverContents::Array(vec![ - lsp::MarkedString::String("Test hover content.".to_string()), - lsp::MarkedString::LanguageString(lsp::LanguageString { - language: "Rust".to_string(), - value: "let foo = 42;".to_string(), - }), - ]), - range: Some(lsp::Range::new( - lsp::Position::new(0, 22), - lsp::Position::new(0, 29), - )), - })) - }, - ); + let mut servers_with_hover_requests = HashMap::default(); + for i in 0..language_server_names.len() { + let new_server = fake_language_servers.next().await.unwrap_or_else(|| { + panic!( + "Failed to get language server #{i} with name {}", + &language_server_names[i] + ) + }); + let new_server_name = new_server.server.name(); + assert!( + !servers_with_hover_requests.contains_key(new_server_name), + "Unexpected: initialized server with the same name twice. Name: `{new_server_name}`" + ); + let new_server_name = new_server_name.to_string(); + match new_server_name.as_str() { + "CrabLang-ls" => { + servers_with_hover_requests.insert( + new_server_name.clone(), + new_server.handle_request::( + move |params, _| { + assert_eq!( + params + .text_document_position_params + .text_document + .uri + .as_str(), + "file:///root-1/main.rs" + ); + let name = new_server_name.clone(); + async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Scalar( + lsp::MarkedString::String(format!("{name} hover")), + ), + range: None, + })) + } + }, + ), + ); + } + "rust-analyzer" => { + servers_with_hover_requests.insert( + new_server_name.clone(), + new_server.handle_request::( + |params, _| async move { + assert_eq!( + params + .text_document_position_params + .text_document + .uri + .as_str(), + "file:///root-1/main.rs" + ); + assert_eq!( + params.text_document_position_params.position, + lsp::Position::new(0, 22) + ); + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Array(vec![ + lsp::MarkedString::String("Test hover content.".to_string()), + lsp::MarkedString::LanguageString(lsp::LanguageString { + language: "Rust".to_string(), + value: "let foo = 42;".to_string(), + }), + ]), + range: Some(lsp::Range::new( + lsp::Position::new(0, 22), + lsp::Position::new(0, 29), + )), + })) + }, + ), + ); + } + unexpected => panic!("Unexpected server name: {unexpected}"), + } + } - let hovers = project_b + // Request hover information as the guest. + let mut hovers = project_b .update(cx_b, |p, cx| p.hover(&buffer_b, 22, cx)) .await; assert_eq!( hovers.len(), - 1, - "Expected exactly one hover but got: {hovers:?}" + 2, + "Expected two hovers from both language servers, but got: {hovers:?}" ); - let hover_info = hovers.into_iter().next().unwrap(); + let _: Vec<()> = futures::future::join_all(servers_with_hover_requests.into_values().map( + |mut hover_request| async move { + hover_request + .next() + .await + .expect("All hover requests should have been triggered") + }, + )) + .await; + + hovers.sort_by_key(|hover| hover.contents.len()); + let first_hover = hovers.first().cloned().unwrap(); + assert_eq!( + first_hover.contents, + vec![project::HoverBlock { + text: "CrabLang-ls hover".to_string(), + kind: HoverBlockKind::Markdown, + },] + ); + let second_hover = hovers.last().cloned().unwrap(); + assert_eq!( + second_hover.contents, + vec![ + project::HoverBlock { + text: "Test hover content.".to_string(), + kind: HoverBlockKind::Markdown, + }, + project::HoverBlock { + text: "let foo = 42;".to_string(), + kind: HoverBlockKind::Code { + language: "Rust".to_string() + }, + } + ] + ); buffer_b.read_with(cx_b, |buffer, _| { let snapshot = buffer.snapshot(); - assert_eq!(hover_info.range.unwrap().to_offset(&snapshot), 22..29); - assert_eq!( - hover_info.contents, - vec![ - project::HoverBlock { - text: "Test hover content.".to_string(), - kind: HoverBlockKind::Markdown, - }, - project::HoverBlock { - text: "let foo = 42;".to_string(), - kind: HoverBlockKind::Code { - language: "Rust".to_string() - }, - } - ] - ); + assert_eq!(second_hover.range.unwrap().to_offset(&snapshot), 22..29); }); } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ea6fdd0e65d163f488e55046322ffd290bbb1695..2b88d1cfa35d315b374473de072fd91cbe70babc 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -117,6 +117,7 @@ pub(crate) struct GetDocumentHighlights { pub position: PointUtf16, } +#[derive(Clone)] pub(crate) struct GetHover { pub position: PointUtf16, } @@ -125,6 +126,7 @@ pub(crate) struct GetCompletions { pub position: PointUtf16, } +#[derive(Clone)] pub(crate) struct GetCodeActions { pub range: Range, pub kinds: Option>, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4d5598621b865bc14f22ae33fb45e4bd7ff17579..9e1ac829646e5ca034aead997f9efd7ab78b1e38 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -26,7 +26,7 @@ use futures::{ mpsc::{self, UnboundedReceiver}, oneshot, }, - future::{try_join_all, Shared}, + future::{join_all, try_join_all, Shared}, select, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, @@ -55,7 +55,7 @@ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, - MessageActionItem, OneOf, ServerHealthStatus, ServerStatus, + MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus, ServerStatus, }; use lsp_command::*; use node_runtime::NodeRuntime; @@ -463,7 +463,7 @@ pub enum HoverBlockKind { Code { language: String }, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Hover { pub contents: Vec, pub range: Option>, @@ -601,6 +601,7 @@ impl Project { client.add_model_message_handler(Self::handle_update_diff_base); client.add_model_request_handler(Self::handle_lsp_command::); client.add_model_request_handler(Self::handle_blame_buffer); + client.add_model_request_handler(Self::handle_multi_lsp_query); } pub fn local( @@ -5215,74 +5216,78 @@ impl Project { position: PointUtf16, cx: &mut ModelContext, ) -> Task> { - fn remove_empty_hover_blocks(mut hover: Hover) -> Option { - hover - .contents - .retain(|hover_block| !hover_block.text.trim().is_empty()); - if hover.contents.is_empty() { - None - } else { - Some(hover) - } - } - if self.is_local() { - let snapshot = buffer.read(cx).snapshot(); - let offset = position.to_offset(&snapshot); - let scope = snapshot.language_scope_at(offset); - - let mut hover_responses = self - .language_servers_for_buffer(buffer.read(cx), cx) - .filter(|(_, server)| match server.capabilities().hover_provider { + let all_actions_task = self.request_multiple_lsp_locally( + &buffer, + Some(position), + |server_capabilities| match server_capabilities.hover_provider { Some(lsp::HoverProviderCapability::Simple(enabled)) => enabled, Some(lsp::HoverProviderCapability::Options(_)) => true, None => false, - }) - .filter(|(adapter, _)| { - scope - .as_ref() - .map(|scope| scope.language_allowed(&adapter.name)) - .unwrap_or(true) - }) - .map(|(_, server)| server.server_id()) - .map(|server_id| { - self.request_lsp( - buffer.clone(), - LanguageServerToQuery::Other(server_id), - GetHover { position }, - cx, - ) - }) - .collect::>(); - - cx.spawn(|_, _| async move { - let mut hovers = Vec::with_capacity(hover_responses.len()); - while let Some(hover_response) = hover_responses.next().await { - if let Some(hover) = hover_response - .log_err() - .flatten() - .and_then(remove_empty_hover_blocks) - { - hovers.push(hover); - } - } - hovers - }) - } else if self.is_remote() { - let request_task = self.request_lsp( - buffer.clone(), - LanguageServerToQuery::Primary, + }, GetHover { position }, cx, ); cx.spawn(|_, _| async move { - request_task + all_actions_task .await - .log_err() - .flatten() - .and_then(remove_empty_hover_blocks) - .map(|hover| vec![hover]) - .unwrap_or_default() + .into_iter() + .filter_map(|hover| remove_empty_hover_blocks(hover?)) + .collect() + }) + } else if let Some(project_id) = self.remote_id() { + let request_task = self.client().request(proto::MultiLspQuery { + buffer_id: buffer.read(cx).remote_id().into(), + version: serialize_version(&buffer.read(cx).version()), + project_id, + strategy: Some(proto::multi_lsp_query::Strategy::All( + proto::AllLanguageServers {}, + )), + request: Some(proto::multi_lsp_query::Request::GetHover( + GetHover { position }.to_proto(project_id, buffer.read(cx)), + )), + }); + let buffer = buffer.clone(); + cx.spawn(|weak_project, cx| async move { + let Some(project) = weak_project.upgrade() else { + return Vec::new(); + }; + join_all( + request_task + .await + .log_err() + .map(|response| response.responses) + .unwrap_or_default() + .into_iter() + .filter_map(|lsp_response| match lsp_response.response? { + proto::lsp_response::Response::GetHoverResponse(response) => { + Some(response) + } + unexpected => { + debug_panic!("Unexpected response: {unexpected:?}"); + None + } + }) + .map(|hover_response| { + let response = GetHover { position }.response_from_proto( + hover_response, + project.clone(), + buffer.clone(), + cx.clone(), + ); + async move { + response + .await + .log_err() + .flatten() + .and_then(remove_empty_hover_blocks) + } + }), + ) + .await + .into_iter() + .flatten() + .collect() }) } else { log::error!("cannot show hovers: project does not have a remote id"); @@ -5651,48 +5656,73 @@ impl Project { cx: &mut ModelContext, ) -> Task> { if self.is_local() { - let snapshot = buffer_handle.read(cx).snapshot(); - let offset = range.start.to_offset(&snapshot); - let scope = snapshot.language_scope_at(offset); - - let mut hover_responses = self - .language_servers_for_buffer(buffer_handle.read(cx), cx) - .filter(|(_, server)| GetCodeActions::supports_code_actions(server.capabilities())) - .filter(|(adapter, _)| { - scope - .as_ref() - .map(|scope| scope.language_allowed(&adapter.name)) - .unwrap_or(true) - }) - .map(|(_, server)| server.server_id()) - .map(|server_id| { - self.request_lsp( - buffer_handle.clone(), - LanguageServerToQuery::Other(server_id), - GetCodeActions { - range: range.clone(), - kinds: None, - }, - cx, - ) - }) - .collect::>(); - - cx.spawn(|_, _| async move { - let mut hovers = Vec::with_capacity(hover_responses.len()); - while let Some(hover_response) = hover_responses.next().await { - hovers.extend(hover_response.log_err().unwrap_or_default()); - } - hovers - }) - } else if self.is_remote() { - let request_task = self.request_lsp( - buffer_handle.clone(), - LanguageServerToQuery::Primary, - GetCodeActions { range, kinds: None }, + let all_actions_task = self.request_multiple_lsp_locally( + &buffer_handle, + Some(range.start), + GetCodeActions::supports_code_actions, + GetCodeActions { + range: range.clone(), + kinds: None, + }, cx, ); - cx.spawn(|_, _| async move { request_task.await.log_err().unwrap_or_default() }) + cx.spawn(|_, _| async move { all_actions_task.await.into_iter().flatten().collect() }) + } else if let Some(project_id) = self.remote_id() { + let request_task = self.client().request(proto::MultiLspQuery { + buffer_id: buffer_handle.read(cx).remote_id().into(), + version: serialize_version(&buffer_handle.read(cx).version()), + project_id, + strategy: Some(proto::multi_lsp_query::Strategy::All( + proto::AllLanguageServers {}, + )), + request: Some(proto::multi_lsp_query::Request::GetCodeActions( + GetCodeActions { + range: range.clone(), + kinds: None, + } + .to_proto(project_id, buffer_handle.read(cx)), + )), + }); + let buffer = buffer_handle.clone(); + cx.spawn(|weak_project, cx| async move { + let Some(project) = weak_project.upgrade() else { + return Vec::new(); + }; + join_all( + request_task + .await + .log_err() + .map(|response| response.responses) + .unwrap_or_default() + .into_iter() + .filter_map(|lsp_response| match lsp_response.response? { + proto::lsp_response::Response::GetCodeActionsResponse(response) => { + Some(response) + } + unexpected => { + debug_panic!("Unexpected response: {unexpected:?}"); + None + } + }) + .map(|code_actions_response| { + let response = GetCodeActions { + range: range.clone(), + kinds: None, + } + .response_from_proto( + code_actions_response, + project.clone(), + buffer.clone(), + cx.clone(), + ); + async move { response.await.log_err().unwrap_or_default() } + }), + ) + .await + .into_iter() + .flatten() + .collect() + }) } else { log::error!("cannot fetch actions: project does not have a remote id"); Task::ready(Vec::new()) @@ -6671,6 +6701,57 @@ impl Project { Task::ready(Ok(Default::default())) } + fn request_multiple_lsp_locally( + &self, + buffer: &Model, + position: Option

, + server_capabilities_check: fn(&ServerCapabilities) -> bool, + request: R, + cx: &mut ModelContext<'_, Self>, + ) -> Task> + where + P: ToOffset, + R: LspCommand + Clone, + ::Result: Send, + ::Params: Send, + { + if !self.is_local() { + debug_panic!("Should not request multiple lsp commands in non-local project"); + return Task::ready(Vec::new()); + } + let snapshot = buffer.read(cx).snapshot(); + let scope = position.and_then(|position| snapshot.language_scope_at(position)); + let mut response_results = self + .language_servers_for_buffer(buffer.read(cx), cx) + .filter(|(_, server)| server_capabilities_check(server.capabilities())) + .filter(|(adapter, _)| { + scope + .as_ref() + .map(|scope| scope.language_allowed(&adapter.name)) + .unwrap_or(true) + }) + .map(|(_, server)| server.server_id()) + .map(|server_id| { + self.request_lsp( + buffer.clone(), + LanguageServerToQuery::Other(server_id), + request.clone(), + cx, + ) + }) + .collect::>(); + + return cx.spawn(|_, _| async move { + let mut responses = Vec::with_capacity(response_results.len()); + while let Some(response_result) = response_results.next().await { + if let Some(response) = response_result.log_err() { + responses.push(response); + } + } + responses + }); + } + fn send_lsp_proto_request( &self, buffer: Model, @@ -7614,6 +7695,118 @@ impl Project { Ok(serialize_blame_buffer_response(blame)) } + async fn handle_multi_lsp_query( + project: Model, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let sender_id = envelope.original_sender_id()?; + let buffer_id = BufferId::new(envelope.payload.buffer_id)?; + let version = deserialize_version(&envelope.payload.version); + let buffer = project.update(&mut cx, |project, _cx| { + project + .opened_buffers + .get(&buffer_id) + .and_then(|buffer| buffer.upgrade()) + .ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id)) + })??; + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(version.clone()) + })? + .await?; + let buffer_version = buffer.update(&mut cx, |buffer, _| buffer.version())?; + match envelope + .payload + .strategy + .context("invalid request without the strategy")? + { + proto::multi_lsp_query::Strategy::All(_) => { + // currently, there's only one multiple language servers query strategy, + // so just ensure it's specified correctly + } + } + match envelope.payload.request { + Some(proto::multi_lsp_query::Request::GetHover(get_hover)) => { + let get_hover = + GetHover::from_proto(get_hover, project.clone(), buffer.clone(), cx.clone()) + .await?; + let all_hovers = project + .update(&mut cx, |project, cx| { + project.request_multiple_lsp_locally( + &buffer, + Some(get_hover.position), + |server_capabilities| match server_capabilities.hover_provider { + Some(lsp::HoverProviderCapability::Simple(enabled)) => enabled, + Some(lsp::HoverProviderCapability::Options(_)) => true, + None => false, + }, + get_hover, + cx, + ) + })? + .await + .into_iter() + .filter_map(|hover| remove_empty_hover_blocks(hover?)); + project.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { + responses: all_hovers + .map(|hover| proto::LspResponse { + response: Some(proto::lsp_response::Response::GetHoverResponse( + GetHover::response_to_proto( + Some(hover), + project, + sender_id, + &buffer_version, + cx, + ), + )), + }) + .collect(), + }) + } + Some(proto::multi_lsp_query::Request::GetCodeActions(get_code_actions)) => { + let get_code_actions = GetCodeActions::from_proto( + get_code_actions, + project.clone(), + buffer.clone(), + cx.clone(), + ) + .await?; + + let all_actions = project + .update(&mut cx, |project, cx| { + project.request_multiple_lsp_locally( + &buffer, + Some(get_code_actions.range.start), + GetCodeActions::supports_code_actions, + get_code_actions, + cx, + ) + })? + .await + .into_iter(); + + project.update(&mut cx, |project, cx| proto::MultiLspQueryResponse { + responses: all_actions + .map(|code_actions| proto::LspResponse { + response: Some(proto::lsp_response::Response::GetCodeActionsResponse( + GetCodeActions::response_to_proto( + code_actions, + project, + sender_id, + &buffer_version, + cx, + ), + )), + }) + .collect(), + }) + } + None => anyhow::bail!("empty multi lsp query request"), + } + } + async fn handle_unshare_project( this: Model, _: TypedEnvelope, @@ -10149,3 +10342,14 @@ fn deserialize_blame_buffer_response(response: proto::BlameBufferResponse) -> gi messages, } } + +fn remove_empty_hover_blocks(mut hover: Hover) -> Option { + hover + .contents + .retain(|hover_block| !hover_block.text.trim().is_empty()); + if hover.contents.is_empty() { + None + } else { + Some(hover) + } +} diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 29ca72fb30f4c7e5d4c6c5b6a1d327695ba052d1..b473213de96cb9e6986ac6147bd9cc7a1fb650d1 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4484,10 +4484,12 @@ async fn test_multiple_language_server_hovers(cx: &mut gpui::TestAppContext) { let mut servers_with_hover_requests = HashMap::default(); for i in 0..language_server_names.len() { - let new_server = fake_tsx_language_servers - .next() - .await - .unwrap_or_else(|| panic!("Failed to get language server #{i}")); + let new_server = fake_tsx_language_servers.next().await.unwrap_or_else(|| { + panic!( + "Failed to get language server #{i} with name {}", + &language_server_names[i] + ) + }); let new_server_name = new_server.server.name(); assert!( !servers_with_hover_requests.contains_key(new_server_name), @@ -4706,10 +4708,12 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) { let mut servers_with_actions_requests = HashMap::default(); for i in 0..language_server_names.len() { - let new_server = fake_tsx_language_servers - .next() - .await - .unwrap_or_else(|| panic!("Failed to get language server #{i}")); + let new_server = fake_tsx_language_servers.next().await.unwrap_or_else(|| { + panic!( + "Failed to get language server #{i} with name {}", + &language_server_names[i] + ) + }); let new_server_name = new_server.server.name(); assert!( !servers_with_actions_requests.contains_key(new_server_name), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 630308b45904f38ba8a89b42d0d92d2b99d12b22..76779c85e338942650cdc1cfb01fd17b5c25e4e8 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -209,8 +209,11 @@ message Envelope { BlameBuffer blame_buffer = 172; BlameBufferResponse blame_buffer_response = 173; - - UpdateNotification update_notification = 174; // current max + + UpdateNotification update_notification = 174; + + MultiLspQuery multi_lsp_query = 175; + MultiLspQueryResponse multi_lsp_query_response = 176; // current max } reserved 158 to 161; @@ -1838,3 +1841,29 @@ message BlameBufferResponse { repeated CommitMessage messages = 2; repeated CommitPermalink permalinks = 3; } + +message MultiLspQuery { + uint64 project_id = 1; + uint64 buffer_id = 2; + repeated VectorClockEntry version = 3; + oneof strategy { + AllLanguageServers all = 4; + } + oneof request { + GetHover get_hover = 5; + GetCodeActions get_code_actions = 6; + } +} + +message AllLanguageServers {} + +message MultiLspQueryResponse { + repeated LspResponse responses = 1; +} + +message LspResponse { + oneof response { + GetHoverResponse get_hover_response = 1; + GetCodeActionsResponse get_code_actions_response = 2; + } +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 89f44faab881b63d1dd63249fd8af14b07be79a7..c5a8f7d32b74c922be9c358ae2ed3da99120c4af 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -299,6 +299,8 @@ messages!( (SetRoomParticipantRole, Foreground), (BlameBuffer, Foreground), (BlameBufferResponse, Foreground), + (MultiLspQuery, Background), + (MultiLspQueryResponse, Background), ); request_messages!( @@ -390,6 +392,7 @@ request_messages!( (LspExtExpandMacro, LspExtExpandMacroResponse), (SetRoomParticipantRole, Ack), (BlameBuffer, BlameBufferResponse), + (MultiLspQuery, MultiLspQueryResponse), ); entity_messages!( @@ -418,6 +421,7 @@ entity_messages!( InlayHints, JoinProject, LeaveProject, + MultiLspQuery, OnTypeFormatting, OpenBufferById, OpenBufferByPath,