lsp: Implement textDocument/signatureHelp for ProjectClientState::Local environment (#12909)

Closes https://github.com/zed-industries/zed/issues/5155
Closes https://github.com/zed-industries/zed/issues/4879


# Purpose
There was no way to know what to put in function signatures or struct
fields other than hovering at the moment. Therefore, it was necessary to
implement LSP's `textDocument/signatureHelp`.

I tried my best to match the surrounding coding style, but since this is
my first contribution, I believe there are various aspects that may be
lacking. I would greatly appreciate your code review.

# Description
When the window is displayed, the current argument or field at the
cursor's position is automatically bolded. If the cursor moves and there
is nothing to display, the window closes automatically.
To minimize changes and reduce the burden of review and debugging, the
SignatureHelp feature is implemented only when `is_local` is `true`.
Some `unimplemented!()` macros are embedded, but rest assured that they
are not called in this implementation.

# How to try it out
Press `cmd + i` (MacOS), `ctrl + i` (Linux).

# Enable auto signature help (2 ways)
### Add `"auto_signature_help": true` to `settings.json`
<img width="426" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/61310c39-47f9-4586-94b0-ae519dc3b37c">

Or

### Press `Auto Signature Help`. (Default `false`)
<img width="226" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/34155215-1eb5-4621-b09b-55df2f1ab6a8">

# Disable to show signature help after completion
### Add `"show_signature_help_after_completion": false` to
`settings.json`
<img width="438" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/5e5bacac-62e0-4921-9243-17e1e72d5eb6">

# Movie

https://github.com/zed-industries/zed/assets/55743826/77c12d51-b0a5-415d-8901-f93ef92098e7

# Screen Shot
<img width="628" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/3ebcf4b6-2b94-4dea-97f9-ac4f33e0291e">

<img width="637" alt="image"
src="https://github.com/zed-industries/zed/assets/55743826/6dc3eb4d-beee-460b-8dbe-d6eec6379b76">

Release Notes:

- Show function signature popovers
([4879](https://github.com/zed-industries/zed/issues/4879),
[5155](https://github.com/zed-industries/zed/issues/5155))

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
tomoikey 2024-07-11 19:38:33 +09:00 committed by GitHub
parent 6a11184ea3
commit 291d64c803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1994 additions and 11 deletions

View file

@ -1,3 +1,5 @@
mod signature_help;
use crate::{
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
@ -6,10 +8,12 @@ use crate::{
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use client::proto::{self, PeerId};
use clock::Global;
use futures::future;
use gpui::{AppContext, AsyncAppContext, Model};
use gpui::{AppContext, AsyncAppContext, FontWeight, Model};
use language::{
language_settings::{language_settings, InlayHintKind},
markdown::{MarkdownHighlight, MarkdownHighlightStyle},
point_from_lsp, point_to_lsp,
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
@ -23,6 +27,10 @@ use lsp::{
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use text::{BufferId, LineEnding};
pub use signature_help::{
SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
};
pub fn lsp_formatting_options(tab_size: u32) -> lsp::FormattingOptions {
lsp::FormattingOptions {
tab_size,
@ -121,6 +129,11 @@ pub(crate) struct GetDocumentHighlights {
pub position: PointUtf16,
}
#[derive(Clone)]
pub(crate) struct GetSignatureHelp {
pub position: PointUtf16,
}
#[derive(Clone)]
pub(crate) struct GetHover {
pub position: PointUtf16,
@ -1225,6 +1238,164 @@ impl LspCommand for GetDocumentHighlights {
}
}
#[async_trait(?Send)]
impl LspCommand for GetSignatureHelp {
type Response = Vec<SignatureHelp>;
type LspRequest = lsp::SignatureHelpRequest;
type ProtoRequest = proto::GetSignatureHelp;
fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
capabilities.signature_help_provider.is_some()
}
fn to_lsp(
&self,
path: &Path,
_: &Buffer,
_: &Arc<LanguageServer>,
_cx: &AppContext,
) -> lsp::SignatureHelpParams {
let url_result = lsp::Url::from_file_path(path);
if url_result.is_err() {
log::error!("an invalid file path has been specified");
}
lsp::SignatureHelpParams {
text_document_position_params: lsp::TextDocumentPositionParams {
text_document: lsp::TextDocumentIdentifier {
uri: url_result.expect("invalid file path"),
},
position: point_to_lsp(self.position),
},
context: None,
work_done_progress_params: Default::default(),
}
}
async fn response_from_lsp(
self,
message: Option<lsp::SignatureHelp>,
_: Model<Project>,
buffer: Model<Buffer>,
_: LanguageServerId,
mut cx: AsyncAppContext,
) -> Result<Self::Response> {
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
Ok(message
.into_iter()
.filter_map(|message| SignatureHelp::new(message, language.clone()))
.collect())
}
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
let offset = buffer.point_utf16_to_offset(self.position);
proto::GetSignatureHelp {
project_id,
buffer_id: buffer.remote_id().to_proto(),
position: Some(serialize_anchor(&buffer.anchor_after(offset))),
version: serialize_version(&buffer.version()),
}
}
async fn from_proto(
payload: Self::ProtoRequest,
_: Model<Project>,
buffer: Model<Buffer>,
mut cx: AsyncAppContext,
) -> Result<Self> {
buffer
.update(&mut cx, |buffer, _| {
buffer.wait_for_version(deserialize_version(&payload.version))
})?
.await
.with_context(|| format!("waiting for version for buffer {}", buffer.entity_id()))?;
let buffer_snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
Ok(Self {
position: payload
.position
.and_then(deserialize_anchor)
.context("invalid position")?
.to_point_utf16(&buffer_snapshot),
})
}
fn response_to_proto(
response: Self::Response,
_: &mut Project,
_: PeerId,
_: &Global,
_: &mut AppContext,
) -> proto::GetSignatureHelpResponse {
proto::GetSignatureHelpResponse {
entries: response
.into_iter()
.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(),
}
}
async fn response_from_proto(
self,
response: proto::GetSignatureHelpResponse,
_: Model<Project>,
_: Model<Buffer>,
_: AsyncAppContext,
) -> Result<Self::Response> {
Ok(response
.entries
.into_iter()
.map(|proto_entry| SignatureHelp {
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> {
BufferId::new(message.buffer_id)
}
}
#[async_trait(?Send)]
impl LspCommand for GetHover {
type Response = Option<Hover>;

View file

@ -0,0 +1,533 @@
use std::{ops::Range, sync::Arc};
use gpui::FontWeight;
use language::{
markdown::{MarkdownHighlight, MarkdownHighlightStyle},
Language,
};
pub const SIGNATURE_HELP_HIGHLIGHT_CURRENT: MarkdownHighlight =
MarkdownHighlight::Style(MarkdownHighlightStyle {
italic: false,
underline: false,
strikethrough: false,
weight: FontWeight::EXTRA_BOLD,
});
pub const SIGNATURE_HELP_HIGHLIGHT_OVERLOAD: MarkdownHighlight =
MarkdownHighlight::Style(MarkdownHighlightStyle {
italic: true,
underline: false,
strikethrough: false,
weight: FontWeight::NORMAL,
});
#[derive(Debug)]
pub struct SignatureHelp {
pub markdown: String,
pub highlights: Vec<(Range<usize>, MarkdownHighlight)>,
}
impl SignatureHelp {
pub fn new(
lsp::SignatureHelp {
signatures,
active_signature,
active_parameter,
..
}: lsp::SignatureHelp,
language: Option<Arc<Language>>,
) -> Option<Self> {
let function_options_count = signatures.len();
let signature_information = active_signature
.and_then(|active_signature| signatures.get(active_signature as usize))
.or_else(|| signatures.first())?;
let str_for_join = ", ";
let parameter_length = signature_information
.parameters
.as_ref()
.map(|parameters| parameters.len())
.unwrap_or(0);
let mut highlight_start = 0;
let (markdown, mut highlights): (Vec<_>, Vec<_>) = signature_information
.parameters
.as_ref()?
.iter()
.enumerate()
.filter_map(|(i, parameter_information)| {
let string = match parameter_information.label.clone() {
lsp::ParameterLabel::Simple(string) => string,
lsp::ParameterLabel::LabelOffsets(offset) => signature_information
.label
.chars()
.skip(offset[0] as usize)
.take((offset[1] - offset[0]) as usize)
.collect::<String>(),
};
let string_length = string.len();
let result = if let Some(active_parameter) = active_parameter {
if i == active_parameter as usize {
Some((
string,
Some((
highlight_start..(highlight_start + string_length),
SIGNATURE_HELP_HIGHLIGHT_CURRENT,
)),
))
} else {
Some((string, None))
}
} else {
Some((string, None))
};
if i != parameter_length {
highlight_start += string_length + str_for_join.len();
}
result
})
.unzip();
let result = if markdown.is_empty() {
None
} else {
let markdown = markdown.join(str_for_join);
let language_name = language
.map(|n| n.name().to_lowercase())
.unwrap_or_default();
let markdown = if function_options_count >= 2 {
let suffix = format!("(+{} overload)", function_options_count - 1);
let highlight_start = markdown.len() + 1;
highlights.push(Some((
highlight_start..(highlight_start + suffix.len()),
SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
)));
format!("```{language_name}\n{markdown} {suffix}")
} else {
format!("```{language_name}\n{markdown}")
};
Some((markdown, highlights.into_iter().flatten().collect()))
};
result.map(|(markdown, highlights)| Self {
markdown,
highlights,
})
}
}
#[cfg(test)]
mod tests {
use crate::lsp_command::signature_help::{
SignatureHelp, SIGNATURE_HELP_HIGHLIGHT_CURRENT, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD,
};
#[test]
fn test_create_signature_help_markdown_string_1() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_2() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(9..18, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_3() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str (+1 overload)".to_string(),
vec![
(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(19..32, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_4() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![
(0..12, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_5() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![
(14..24, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_6() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(1),
active_parameter: None,
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nhoge: String, fuga: bool (+1 overload)".to_string(),
vec![(25..38, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_7() {
let signature_help = lsp::SignatureHelp {
signatures: vec![
lsp::SignatureInformation {
label: "fn test1(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("bar: &str".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test2(hoge: String, fuga: bool)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("hoge: String".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("fuga: bool".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
lsp::SignatureInformation {
label: "fn test3(one: usize, two: u32)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("one: usize".to_string()),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::Simple("two: u32".to_string()),
documentation: None,
},
]),
active_parameter: None,
},
],
active_signature: Some(2),
active_parameter: Some(1),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\none: usize, two: u32 (+2 overload)".to_string(),
vec![
(12..20, SIGNATURE_HELP_HIGHLIGHT_CURRENT),
(21..34, SIGNATURE_HELP_HIGHLIGHT_OVERLOAD)
]
)
);
}
#[test]
fn test_create_signature_help_markdown_string_8() {
let signature_help = lsp::SignatureHelp {
signatures: vec![],
active_signature: None,
active_parameter: None,
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_none());
}
#[test]
fn test_create_signature_help_markdown_string_9() {
let signature_help = lsp::SignatureHelp {
signatures: vec![lsp::SignatureInformation {
label: "fn test(foo: u8, bar: &str)".to_string(),
documentation: None,
parameters: Some(vec![
lsp::ParameterInformation {
label: lsp::ParameterLabel::LabelOffsets([8, 15]),
documentation: None,
},
lsp::ParameterInformation {
label: lsp::ParameterLabel::LabelOffsets([17, 26]),
documentation: None,
},
]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: Some(0),
};
let maybe_markdown = SignatureHelp::new(signature_help, None);
assert!(maybe_markdown.is_some());
let markdown = maybe_markdown.unwrap();
let markdown = (markdown.markdown, markdown.highlights);
assert_eq!(
markdown,
(
"```\nfoo: u8, bar: &str".to_string(),
vec![(0..7, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]
)
);
}
}

View file

@ -709,6 +709,7 @@ impl Project {
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_lsp_command::<LinkedEditingRange>);
client.add_model_request_handler(Self::handle_signature_help);
}
pub fn local(
@ -5778,6 +5779,63 @@ impl Project {
}
}
pub fn signature_help<T: ToPointUtf16>(
&self,
buffer: &Model<Buffer>,
position: T,
cx: &mut ModelContext<Self>,
) -> Task<Vec<SignatureHelp>> {
let position = position.to_point_utf16(buffer.read(cx));
if self.is_local() {
let all_actions_task = self.request_multiple_lsp_locally(
buffer,
Some(position),
|server_capabilities| server_capabilities.signature_help_provider.is_some(),
GetSignatureHelp { position },
cx,
);
cx.spawn(|_, _| async move {
all_actions_task
.await
.into_iter()
.flatten()
.filter(|help| !help.markdown.is_empty())
.collect::<Vec<_>>()
})
} else if let Some(project_id) = self.remote_id() {
let position_anchor = buffer
.read(cx)
.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()),
});
let buffer = buffer.clone();
cx.spawn(move |project, cx| async move {
let Some(response) = request.await.log_err() else {
return Vec::new();
};
let Some(project) = project.upgrade() else {
return Vec::new();
};
GetSignatureHelp::response_from_proto(
GetSignatureHelp { position },
response,
project,
buffer,
cx,
)
.await
.log_err()
.unwrap_or_default()
})
} else {
Task::ready(Vec::new())
}
}
fn hover_impl(
&self,
buffer: &Model<Buffer>,
@ -9851,6 +9909,43 @@ impl Project {
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, _| {
project
.opened_buffers
.get(&buffer_id)
.and_then(|buffer| buffer.upgrade())
.with_context(|| format!("unknown buffer id {}", envelope.payload.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(
lang_server: &LanguageServer,
action: &mut CodeAction,