Tidy up collab-related signature help data (#14377)

Follow-up of https://github.com/zed-industries/zed/pull/12909

* Fully preserve LSP data when sending it via collab, and only strip it
on the client.
* Avoid extra custom request handlers, and extend multi LSP server query
protocol instead.


Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2024-07-13 04:06:01 +03:00 committed by GitHub
parent dd63e25f23
commit 9ce989a704
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 269 additions and 167 deletions

View file

@ -642,10 +642,7 @@ impl Server {
app_state.config.openai_api_key.clone(), app_state.config.openai_api_key.clone(),
) )
}) })
}) });
.add_request_handler(user_handler(
forward_read_only_project_request::<proto::GetSignatureHelp>,
));
Arc::new(server) Arc::new(server)
} }

View file

@ -10,10 +10,9 @@ use async_trait::async_trait;
use client::proto::{self, PeerId}; use client::proto::{self, PeerId};
use clock::Global; use clock::Global;
use futures::future; use futures::future;
use gpui::{AppContext, AsyncAppContext, FontWeight, Model}; use gpui::{AppContext, AsyncAppContext, Model};
use language::{ use language::{
language_settings::{language_settings, InlayHintKind}, language_settings::{language_settings, InlayHintKind},
markdown::{MarkdownHighlight, MarkdownHighlightStyle},
point_from_lsp, point_to_lsp, point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
@ -24,6 +23,7 @@ use lsp::{
DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
OneOf, ServerCapabilities, OneOf, ServerCapabilities,
}; };
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use text::{BufferId, LineEnding}; use text::{BufferId, LineEnding};
@ -1242,7 +1242,7 @@ impl LspCommand for GetDocumentHighlights {
#[async_trait(?Send)] #[async_trait(?Send)]
impl LspCommand for GetSignatureHelp { impl LspCommand for GetSignatureHelp {
type Response = Vec<SignatureHelp>; type Response = Option<SignatureHelp>;
type LspRequest = lsp::SignatureHelpRequest; type LspRequest = lsp::SignatureHelpRequest;
type ProtoRequest = proto::GetSignatureHelp; type ProtoRequest = proto::GetSignatureHelp;
@ -1283,10 +1283,7 @@ impl LspCommand for GetSignatureHelp {
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Self::Response> { ) -> Result<Self::Response> {
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?; let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
Ok(message Ok(message.and_then(|message| SignatureHelp::new(message, language)))
.into_iter()
.filter_map(|message| SignatureHelp::new(message, language.clone()))
.collect())
} }
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest { fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
@ -1329,34 +1326,8 @@ impl LspCommand for GetSignatureHelp {
_: &mut AppContext, _: &mut AppContext,
) -> proto::GetSignatureHelpResponse { ) -> proto::GetSignatureHelpResponse {
proto::GetSignatureHelpResponse { proto::GetSignatureHelpResponse {
entries: response signature_help: response
.into_iter() .map(|signature_help| lsp_to_proto_signature(signature_help.original_data)),
.map(|signature_help| proto::SignatureHelp {
rendered_text: signature_help.markdown,
highlights: signature_help
.highlights
.into_iter()
.filter_map(|(range, highlight)| {
let MarkdownHighlight::Style(highlight) = highlight else {
return None;
};
Some(proto::HighlightedRange {
range: Some(proto::Range {
start: range.start as u64,
end: range.end as u64,
}),
highlight: Some(proto::MarkdownHighlight {
italic: highlight.italic,
underline: highlight.underline,
strikethrough: highlight.strikethrough,
weight: highlight.weight.0,
}),
})
})
.collect(),
})
.collect(),
} }
} }
@ -1364,33 +1335,14 @@ impl LspCommand for GetSignatureHelp {
self, self,
response: proto::GetSignatureHelpResponse, response: proto::GetSignatureHelpResponse,
_: Model<Project>, _: Model<Project>,
_: Model<Buffer>, buffer: Model<Buffer>,
_: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<Self::Response> { ) -> Result<Self::Response> {
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
Ok(response Ok(response
.entries .signature_help
.into_iter() .map(|proto_help| proto_to_lsp_signature(proto_help))
.map(|proto_entry| SignatureHelp { .and_then(|lsp_help| SignatureHelp::new(lsp_help, language)))
markdown: proto_entry.rendered_text,
highlights: proto_entry
.highlights
.into_iter()
.filter_map(|highlight| {
let proto_highlight = highlight.highlight?;
let range = highlight.range?;
Some((
range.start as usize..range.end as usize,
MarkdownHighlight::Style(MarkdownHighlightStyle {
italic: proto_highlight.italic,
underline: proto_highlight.underline,
strikethrough: proto_highlight.strikethrough,
weight: FontWeight(proto_highlight.weight),
}),
))
})
.collect(),
})
.collect())
} }
fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> { fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {

View file

@ -5,6 +5,7 @@ use language::{
markdown::{MarkdownHighlight, MarkdownHighlightStyle}, markdown::{MarkdownHighlight, MarkdownHighlightStyle},
Language, Language,
}; };
use rpc::proto::{self, documentation};
pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight = pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight =
MarkdownHighlight::Style(MarkdownHighlightStyle { MarkdownHighlight::Style(MarkdownHighlightStyle {
@ -26,38 +27,31 @@ pub const SIGNATURE_HELP_HIGHLIGHT_OVERLOAD: MarkdownHighlight =
pub struct SignatureHelp { pub struct SignatureHelp {
pub markdown: String, pub markdown: String,
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>, pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
pub(super) original_data: lsp::SignatureHelp,
} }
impl SignatureHelp { impl SignatureHelp {
pub fn new( pub fn new(help: lsp::SignatureHelp, language: Option<Arc<Language>>) -> Option<Self> {
lsp::SignatureHelp { let function_options_count = help.signatures.len();
signatures,
active_signature,
active_parameter,
..
}: lsp::SignatureHelp,
language: Option<Arc<Language>>,
) -> Option<Self> {
let function_options_count = signatures.len();
let signature_information = active_signature let signature_information = help
.and_then(|active_signature| signatures.get(active_signature as usize)) .active_signature
.or_else(|| signatures.first())?; .and_then(|active_signature| help.signatures.get(active_signature as usize))
.or_else(|| help.signatures.first())?;
let str_for_join = ", "; let str_for_join = ", ";
let parameter_length = signature_information let parameter_length = signature_information
.parameters .parameters
.as_ref() .as_ref()
.map(|parameters| parameters.len()) .map_or(0, |parameters| parameters.len());
.unwrap_or(0);
let mut highlight_start = 0; let mut highlight_start = 0;
let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information
.parameters .parameters
.as_ref()? .as_ref()?
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, parameter_information)| { .map(|(i, parameter_information)| {
let string = match parameter_information.label.clone() { let label = match parameter_information.label.clone() {
lsp::ParameterLabel::Simple(string) => string, lsp::ParameterLabel::Simple(string) => string,
lsp::ParameterLabel::LabelOffsets(offset) => signature_information lsp::ParameterLabel::LabelOffsets(offset) => signature_information
.label .label
@ -66,33 +60,28 @@ impl SignatureHelp {
.take((offset[1] - offset[0]) as usize) .take((offset[1] - offset[0]) as usize)
.collect::<String>(), .collect::<String>(),
}; };
let string_length = string.len(); let label_length = label.len();
let result = if let Some(active_parameter) = active_parameter { let highlights = help.active_parameter.and_then(|active_parameter| {
if i == active_parameter as usize { if i == active_parameter as usize {
Some(( Some((
string, highlight_start..(highlight_start + label_length),
Some(( SIGNATURE_HELP_HIGHLIGHT_CURRENT,
highlight_start..(highlight_start + string_length),
SIGNATURE_HELP_HIGHLIGHT_CURRENT,
)),
)) ))
} else { } else {
Some((string, None)) None
} }
} else { });
Some((string, None))
};
if i != parameter_length { if i != parameter_length {
highlight_start += string_length + str_for_join.len(); highlight_start += label_length + str_for_join.len();
} }
result (label, highlights)
}) })
.unzip(); .unzip();
let result = if markdown.is_empty() { if markdown.is_empty() {
None None
} else { } else {
let markdown = markdown.join(str_for_join); let markdown = markdown.join(str_for_join);
@ -112,16 +101,130 @@ impl SignatureHelp {
format!("```{language_name}\n{markdown}") format!("```{language_name}\n{markdown}")
}; };
Some((markdown, highlights.into_iter().flatten().collect())) Some(Self {
}; markdown,
highlights: highlights.into_iter().flatten().collect(),
result.map(|(markdown, highlights)| Self { original_data: help,
markdown, })
highlights, }
})
} }
} }
pub fn lsp_to_proto_signature(lsp_help: lsp::SignatureHelp) -> proto::SignatureHelp {
proto::SignatureHelp {
signatures: lsp_help
.signatures
.into_iter()
.map(|signature| proto::SignatureInformation {
label: signature.label,
documentation: signature
.documentation
.map(|documentation| lsp_to_proto_documentation(documentation)),
parameters: signature
.parameters
.unwrap_or_default()
.into_iter()
.map(|parameter_info| proto::ParameterInformation {
label: Some(match parameter_info.label {
lsp::ParameterLabel::Simple(label) => {
proto::parameter_information::Label::Simple(label)
}
lsp::ParameterLabel::LabelOffsets(offsets) => {
proto::parameter_information::Label::LabelOffsets(
proto::LabelOffsets {
start: offsets[0],
end: offsets[1],
},
)
}
}),
documentation: parameter_info.documentation.map(lsp_to_proto_documentation),
})
.collect(),
active_parameter: signature.active_parameter,
})
.collect(),
active_signature: lsp_help.active_signature,
active_parameter: lsp_help.active_parameter,
}
}
fn lsp_to_proto_documentation(documentation: lsp::Documentation) -> proto::Documentation {
proto::Documentation {
content: Some(match documentation {
lsp::Documentation::String(string) => proto::documentation::Content::Value(string),
lsp::Documentation::MarkupContent(content) => {
proto::documentation::Content::MarkupContent(proto::MarkupContent {
is_markdown: matches!(content.kind, lsp::MarkupKind::Markdown),
value: content.value,
})
}
}),
}
}
pub fn proto_to_lsp_signature(proto_help: proto::SignatureHelp) -> lsp::SignatureHelp {
lsp::SignatureHelp {
signatures: proto_help
.signatures
.into_iter()
.map(|signature| lsp::SignatureInformation {
label: signature.label,
documentation: signature.documentation.and_then(proto_to_lsp_documentation),
parameters: Some(
signature
.parameters
.into_iter()
.filter_map(|parameter_info| {
Some(lsp::ParameterInformation {
label: match parameter_info.label? {
proto::parameter_information::Label::Simple(string) => {
lsp::ParameterLabel::Simple(string)
}
proto::parameter_information::Label::LabelOffsets(offsets) => {
lsp::ParameterLabel::LabelOffsets([
offsets.start,
offsets.end,
])
}
},
documentation: parameter_info
.documentation
.and_then(proto_to_lsp_documentation),
})
})
.collect(),
),
active_parameter: signature.active_parameter,
})
.collect(),
active_signature: proto_help.active_signature,
active_parameter: proto_help.active_parameter,
}
}
fn proto_to_lsp_documentation(documentation: proto::Documentation) -> Option<lsp::Documentation> {
let documentation = {
Some(match documentation.content? {
documentation::Content::Value(string) => lsp::Documentation::String(string),
documentation::Content::MarkupContent(markup) => {
lsp::Documentation::MarkupContent(if markup.is_markdown {
lsp::MarkupContent {
kind: lsp::MarkupKind::Markdown,
value: markup.value,
}
} else {
lsp::MarkupContent {
kind: lsp::MarkupKind::PlainText,
value: markup.value,
}
})
}
})
};
documentation
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::lsp_command::signature_help::{ use crate::lsp_command::signature_help::{

View file

@ -695,7 +695,6 @@ impl Project {
client.add_model_request_handler(Self::handle_task_context_for_location); client.add_model_request_handler(Self::handle_task_context_for_location);
client.add_model_request_handler(Self::handle_task_templates); client.add_model_request_handler(Self::handle_task_templates);
client.add_model_request_handler(Self::handle_lsp_command::<LinkedEditingRange>); client.add_model_request_handler(Self::handle_lsp_command::<LinkedEditingRange>);
client.add_model_request_handler(Self::handle_signature_help);
} }
pub fn local( pub fn local(
@ -5464,33 +5463,52 @@ impl Project {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
} else if let Some(project_id) = self.remote_id() { } else if let Some(project_id) = self.remote_id() {
let position_anchor = buffer let request_task = self.client().request(proto::MultiLspQuery {
.read(cx) buffer_id: buffer.read(cx).remote_id().into(),
.anchor_at(buffer.read(cx).point_utf16_to_offset(position), Bias::Right);
let request = self.client.request(proto::GetSignatureHelp {
project_id,
position: Some(serialize_anchor(&position_anchor)),
buffer_id: buffer.read(cx).remote_id().to_proto(),
version: serialize_version(&buffer.read(cx).version()), 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::GetSignatureHelp(
GetSignatureHelp { position }.to_proto(project_id, buffer.read(cx)),
)),
}); });
let buffer = buffer.clone(); let buffer = buffer.clone();
cx.spawn(move |project, cx| async move { cx.spawn(|weak_project, cx| async move {
let Some(response) = request.await.log_err() else { let Some(project) = weak_project.upgrade() else {
return Vec::new(); return Vec::new();
}; };
let Some(project) = project.upgrade() else { join_all(
return Vec::new(); request_task
}; .await
GetSignatureHelp::response_from_proto( .log_err()
GetSignatureHelp { position }, .map(|response| response.responses)
response, .unwrap_or_default()
project, .into_iter()
buffer, .filter_map(|lsp_response| match lsp_response.response? {
cx, proto::lsp_response::Response::GetSignatureHelpResponse(response) => {
Some(response)
}
unexpected => {
debug_panic!("Unexpected response: {unexpected:?}");
None
}
})
.map(|signature_response| {
let response = GetSignatureHelp { position }.response_from_proto(
signature_response,
project.clone(),
buffer.clone(),
cx.clone(),
);
async move { response.await.log_err().flatten() }
}),
) )
.await .await
.log_err() .into_iter()
.unwrap_or_default() .flatten()
.collect()
}) })
} else { } else {
Task::ready(Vec::new()) Task::ready(Vec::new())
@ -8356,6 +8374,48 @@ impl Project {
.collect(), .collect(),
}) })
} }
Some(proto::multi_lsp_query::Request::GetSignatureHelp(get_signature_help)) => {
let get_signature_help = GetSignatureHelp::from_proto(
get_signature_help,
project.clone(),
buffer.clone(),
cx.clone(),
)
.await?;
let all_signatures = project
.update(&mut cx, |project, cx| {
project.request_multiple_lsp_locally(
&buffer,
Some(get_signature_help.position),
|server_capabilities| {
server_capabilities.signature_help_provider.is_some()
},
get_signature_help,
cx,
)
})?
.await
.into_iter();
project.update(&mut cx, |project, cx| proto::MultiLspQueryResponse {
responses: all_signatures
.map(|signature_help| proto::LspResponse {
response: Some(
proto::lsp_response::Response::GetSignatureHelpResponse(
GetSignatureHelp::response_to_proto(
signature_help,
project,
sender_id,
&buffer_version,
cx,
),
),
),
})
.collect(),
})
}
None => anyhow::bail!("empty multi lsp query request"), None => anyhow::bail!("empty multi lsp query request"),
} }
} }
@ -9322,39 +9382,6 @@ impl Project {
Ok(proto::TaskTemplatesResponse { templates }) Ok(proto::TaskTemplatesResponse { templates })
} }
async fn handle_signature_help(
project: Model<Self>,
envelope: TypedEnvelope<proto::GetSignatureHelp>,
mut cx: AsyncAppContext,
) -> Result<proto::GetSignatureHelpResponse> {
let sender_id = envelope.original_sender_id()?;
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
let buffer = project.update(&mut cx, |project, cx| {
project.buffer_store.read(cx).get_existing(buffer_id)
})??;
let response = GetSignatureHelp::from_proto(
envelope.payload.clone(),
project.clone(),
buffer.clone(),
cx.clone(),
)
.await?;
let help_response = project
.update(&mut cx, |project, cx| {
project.signature_help(&buffer, response.position, cx)
})?
.await;
project.update(&mut cx, |project, cx| {
GetSignatureHelp::response_to_proto(
help_response,
project,
sender_id,
&buffer.read(cx).version(),
cx,
)
})
}
async fn try_resolve_code_action( async fn try_resolve_code_action(
lang_server: &LanguageServer, lang_server: &LanguageServer,
action: &mut CodeAction, action: &mut CodeAction,

View file

@ -945,24 +945,45 @@ message GetSignatureHelp {
} }
message GetSignatureHelpResponse { message GetSignatureHelpResponse {
repeated SignatureHelp entries = 1; optional SignatureHelp signature_help = 1;
} }
message SignatureHelp { message SignatureHelp {
string rendered_text = 1; repeated SignatureInformation signatures = 1;
repeated HighlightedRange highlights = 2; optional uint32 active_signature = 2;
optional uint32 active_parameter = 3;
} }
message HighlightedRange { message SignatureInformation {
Range range = 1; string label = 1;
MarkdownHighlight highlight = 2; optional Documentation documentation = 2;
repeated ParameterInformation parameters = 3;
optional uint32 active_parameter = 4;
} }
message MarkdownHighlight { message Documentation {
bool italic = 1; oneof content {
bool underline = 2; string value = 1;
bool strikethrough = 3; MarkupContent markup_content = 2;
float weight = 4; }
}
enum MarkupKind {
PlainText = 0;
Markdown = 1;
}
message ParameterInformation {
oneof label {
string simple = 1;
LabelOffsets label_offsets = 2;
}
optional Documentation documentation = 3;
}
message LabelOffsets {
uint32 start = 1;
uint32 end = 2;
} }
message GetHover { message GetHover {
@ -2166,6 +2187,7 @@ message MultiLspQuery {
oneof request { oneof request {
GetHover get_hover = 5; GetHover get_hover = 5;
GetCodeActions get_code_actions = 6; GetCodeActions get_code_actions = 6;
GetSignatureHelp get_signature_help = 7;
} }
} }
@ -2184,6 +2206,7 @@ message LspResponse {
oneof response { oneof response {
GetHoverResponse get_hover_response = 1; GetHoverResponse get_hover_response = 1;
GetCodeActionsResponse get_code_actions_response = 2; GetCodeActionsResponse get_code_actions_response = 2;
GetSignatureHelpResponse get_signature_help_response = 3;
} }
} }