Return back Rust completion details (#22648)

Closes https://github.com/zed-industries/zed/issues/22642

In Zed, Rust's label generators expected the details to come in ` (use
std.foo.Bar)` form, but recently, r-a started to send these details
without the leading whitespace which broke the code generation.

The PR makes LSP results parsing more lenient to work with both details'
forms.

Release Notes:

- Fixed Rust completion labels not showing the imports
This commit is contained in:
Kirill Bulatov 2025-01-04 13:15:09 +02:00 committed by GitHub
parent 5f1eee3c66
commit 8151dc7696
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -253,49 +253,51 @@ impl LspAdapter for RustLspAdapter {
.as_ref() .as_ref()
.and_then(|detail| detail.detail.as_ref()) .and_then(|detail| detail.detail.as_ref())
.or(completion.detail.as_ref()) .or(completion.detail.as_ref())
.map(ToOwned::to_owned); .map(|detail| detail.trim());
let function_signature = completion let function_signature = completion
.label_details .label_details
.as_ref() .as_ref()
.and_then(|detail| detail.description.as_ref()) .and_then(|detail| detail.description.as_deref())
.or(completion.detail.as_ref()) .or(completion.detail.as_deref());
.map(ToOwned::to_owned); match (detail, completion.kind) {
match completion.kind { (Some(detail), Some(lsp::CompletionItemKind::FIELD)) => {
Some(lsp::CompletionItemKind::FIELD) if detail.is_some() => {
let name = &completion.label; let name = &completion.label;
let text = format!("{}: {}", name, detail.unwrap()); let text = format!("{name}: {detail}");
let source = Rope::from(format!("struct S {{ {} }}", text).as_str()); let prefix = "struct S { ";
let runs = language.highlight_text(&source, 11..11 + text.len()); let source = Rope::from(format!("{prefix}{text} }}"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel { return Some(CodeLabel {
text, text,
runs, runs,
filter_range: 0..name.len(), filter_range: 0..name.len(),
}); });
} }
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE) (
if detail.is_some() Some(detail),
&& completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
{ ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
let name = &completion.label; let name = &completion.label;
let text = format!( let text = format!(
"{}: {}", "{}: {}",
name, name,
completion.detail.as_ref().or(detail.as_ref()).unwrap() completion.detail.as_deref().unwrap_or(detail)
); );
let source = Rope::from(format!("let {} = ();", text).as_str()); let prefix = "let ";
let runs = language.highlight_text(&source, 4..4 + text.len()); let source = Rope::from(format!("{prefix}{text} = ();"));
let runs =
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
return Some(CodeLabel { return Some(CodeLabel {
text, text,
runs, runs,
filter_range: 0..name.len(), filter_range: 0..name.len(),
}); });
} }
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD) (
if detail.is_some() => Some(detail),
{ Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
) => {
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap());
let detail = detail.unwrap();
const FUNCTION_PREFIXES: [&str; 6] = [ const FUNCTION_PREFIXES: [&str; 6] = [
"async fn", "async fn",
"async unsafe fn", "async unsafe fn",
@ -315,10 +317,11 @@ impl LspAdapter for RustLspAdapter {
// fn keyword should be followed by opening parenthesis. // fn keyword should be followed by opening parenthesis.
if let Some((prefix, suffix)) = fn_keyword { if let Some((prefix, suffix)) = fn_keyword {
let mut text = REGEX.replace(&completion.label, suffix).to_string(); let mut text = REGEX.replace(&completion.label, suffix).to_string();
let source = Rope::from(format!("{prefix} {} {{}}", text).as_str()); let source = Rope::from(format!("{prefix} {text} {{}}"));
let run_start = prefix.len() + 1; let run_start = prefix.len() + 1;
let runs = language.highlight_text(&source, run_start..run_start + text.len()); let runs = language.highlight_text(&source, run_start..run_start + text.len());
if detail.starts_with("(") { if detail.starts_with("(") {
text.push(' ');
text.push_str(&detail); text.push_str(&detail);
} }
@ -342,7 +345,7 @@ impl LspAdapter for RustLspAdapter {
}); });
} }
} }
Some(kind) => { (_, Some(kind)) => {
let highlight_name = match kind { let highlight_name = match kind {
lsp::CompletionItemKind::STRUCT lsp::CompletionItemKind::STRUCT
| lsp::CompletionItemKind::INTERFACE | lsp::CompletionItemKind::INTERFACE
@ -357,8 +360,8 @@ impl LspAdapter for RustLspAdapter {
let mut label = completion.label.clone(); let mut label = completion.label.clone();
if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) { if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) {
use std::fmt::Write; label.push(' ');
write!(label, "{detail}").ok()?; label.push_str(detail);
} }
let mut label = CodeLabel::plain(label, None); let mut label = CodeLabel::plain(label, None);
if let Some(highlight_name) = highlight_name { if let Some(highlight_name) = highlight_name {