languages: Fix rust completions not having proper detail labels (#35772)
rust-analyzer changed the format here a bit some months ago which partially broke our nice detailed highlighted completion labels. The brings that back while also cleaning up the code a bit. Also fixes a bug where disabling rust-analyzers snippet callable completions would fully break them. Release Notes: - N/A
This commit is contained in:
parent
5b1b3c51d4
commit
f5f837d39a
1 changed files with 112 additions and 90 deletions
|
@ -305,66 +305,63 @@ impl LspAdapter for RustLspAdapter {
|
||||||
completion: &lsp::CompletionItem,
|
completion: &lsp::CompletionItem,
|
||||||
language: &Arc<Language>,
|
language: &Arc<Language>,
|
||||||
) -> Option<CodeLabel> {
|
) -> Option<CodeLabel> {
|
||||||
let detail = completion
|
// rust-analyzer calls these detail left and detail right in terms of where it expects things to be rendered
|
||||||
|
// this usually contains signatures of the thing to be completed
|
||||||
|
let detail_right = completion
|
||||||
.label_details
|
.label_details
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|detail| detail.detail.as_ref())
|
.and_then(|detail| detail.description.as_ref())
|
||||||
.or(completion.detail.as_ref())
|
.or(completion.detail.as_ref())
|
||||||
.map(|detail| detail.trim());
|
.map(|detail| detail.trim());
|
||||||
let function_signature = completion
|
// this tends to contain alias and import information
|
||||||
|
let detail_left = completion
|
||||||
.label_details
|
.label_details
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|detail| detail.description.as_deref())
|
.and_then(|detail| detail.detail.as_deref());
|
||||||
.or(completion.detail.as_deref());
|
let mk_label = |text: String, runs| {
|
||||||
match (detail, completion.kind) {
|
let filter_range = completion
|
||||||
(Some(detail), Some(lsp::CompletionItemKind::FIELD)) => {
|
.filter_text
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|filter| {
|
||||||
|
completion
|
||||||
|
.label
|
||||||
|
.find(filter)
|
||||||
|
.map(|ix| ix..ix + filter.len())
|
||||||
|
})
|
||||||
|
.unwrap_or(0..completion.label.len());
|
||||||
|
|
||||||
|
CodeLabel {
|
||||||
|
text,
|
||||||
|
runs,
|
||||||
|
filter_range,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut label = match (detail_right, completion.kind) {
|
||||||
|
(Some(signature), Some(lsp::CompletionItemKind::FIELD)) => {
|
||||||
let name = &completion.label;
|
let name = &completion.label;
|
||||||
let text = format!("{name}: {detail}");
|
let text = format!("{name}: {signature}");
|
||||||
let prefix = "struct S { ";
|
let prefix = "struct S { ";
|
||||||
let source = Rope::from(format!("{prefix}{text} }}"));
|
let source = Rope::from(format!("{prefix}{text} }}"));
|
||||||
let runs =
|
let runs =
|
||||||
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
|
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
|
||||||
let filter_range = completion
|
mk_label(text, runs)
|
||||||
.filter_text
|
|
||||||
.as_deref()
|
|
||||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
|
||||||
.unwrap_or(0..name.len());
|
|
||||||
return Some(CodeLabel {
|
|
||||||
text,
|
|
||||||
runs,
|
|
||||||
filter_range,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Some(detail),
|
Some(signature),
|
||||||
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
|
Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE),
|
||||||
) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
|
) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => {
|
||||||
let name = &completion.label;
|
let name = &completion.label;
|
||||||
let text = format!(
|
let text = format!("{name}: {signature}",);
|
||||||
"{}: {}",
|
|
||||||
name,
|
|
||||||
completion.detail.as_deref().unwrap_or(detail)
|
|
||||||
);
|
|
||||||
let prefix = "let ";
|
let prefix = "let ";
|
||||||
let source = Rope::from(format!("{prefix}{text} = ();"));
|
let source = Rope::from(format!("{prefix}{text} = ();"));
|
||||||
let runs =
|
let runs =
|
||||||
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
|
language.highlight_text(&source, prefix.len()..prefix.len() + text.len());
|
||||||
let filter_range = completion
|
mk_label(text, runs)
|
||||||
.filter_text
|
|
||||||
.as_deref()
|
|
||||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
|
||||||
.unwrap_or(0..name.len());
|
|
||||||
return Some(CodeLabel {
|
|
||||||
text,
|
|
||||||
runs,
|
|
||||||
filter_range,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Some(detail),
|
function_signature,
|
||||||
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
|
Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD),
|
||||||
) => {
|
) => {
|
||||||
static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap());
|
|
||||||
const FUNCTION_PREFIXES: [&str; 6] = [
|
const FUNCTION_PREFIXES: [&str; 6] = [
|
||||||
"async fn",
|
"async fn",
|
||||||
"async unsafe fn",
|
"async unsafe fn",
|
||||||
|
@ -373,34 +370,27 @@ impl LspAdapter for RustLspAdapter {
|
||||||
"unsafe fn",
|
"unsafe fn",
|
||||||
"fn",
|
"fn",
|
||||||
];
|
];
|
||||||
// Is it function `async`?
|
let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| {
|
||||||
let fn_keyword = FUNCTION_PREFIXES.iter().find_map(|prefix| {
|
function_signature?
|
||||||
function_signature.as_ref().and_then(|signature| {
|
.strip_prefix(prefix)
|
||||||
signature
|
.map(|suffix| (prefix, suffix))
|
||||||
.strip_prefix(*prefix)
|
|
||||||
.map(|suffix| (*prefix, suffix))
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
// 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_prefixed {
|
||||||
let mut text = REGEX.replace(&completion.label, suffix).to_string();
|
let label = if let Some(label) = completion
|
||||||
|
.label
|
||||||
|
.strip_suffix("(…)")
|
||||||
|
.or_else(|| completion.label.strip_suffix("()"))
|
||||||
|
{
|
||||||
|
label
|
||||||
|
} else {
|
||||||
|
&completion.label
|
||||||
|
};
|
||||||
|
let text = format!("{label}{suffix}");
|
||||||
let source = Rope::from(format!("{prefix} {text} {{}}"));
|
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("(") {
|
mk_label(text, runs)
|
||||||
text.push(' ');
|
|
||||||
text.push_str(&detail);
|
|
||||||
}
|
|
||||||
let filter_range = completion
|
|
||||||
.filter_text
|
|
||||||
.as_deref()
|
|
||||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
|
||||||
.unwrap_or(0..completion.label.find('(').unwrap_or(text.len()));
|
|
||||||
return Some(CodeLabel {
|
|
||||||
filter_range,
|
|
||||||
text,
|
|
||||||
runs,
|
|
||||||
});
|
|
||||||
} else if completion
|
} else if completion
|
||||||
.detail
|
.detail
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -410,20 +400,13 @@ impl LspAdapter for RustLspAdapter {
|
||||||
let len = text.len();
|
let len = text.len();
|
||||||
let source = Rope::from(text.as_str());
|
let source = Rope::from(text.as_str());
|
||||||
let runs = language.highlight_text(&source, 0..len);
|
let runs = language.highlight_text(&source, 0..len);
|
||||||
let filter_range = completion
|
mk_label(text, runs)
|
||||||
.filter_text
|
} else {
|
||||||
.as_deref()
|
mk_label(completion.label.clone(), vec![])
|
||||||
.and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len()))
|
|
||||||
.unwrap_or(0..len);
|
|
||||||
return Some(CodeLabel {
|
|
||||||
filter_range,
|
|
||||||
text,
|
|
||||||
runs,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, Some(kind)) => {
|
(_, kind) => {
|
||||||
let highlight_name = match kind {
|
let highlight_name = kind.and_then(|kind| match kind {
|
||||||
lsp::CompletionItemKind::STRUCT
|
lsp::CompletionItemKind::STRUCT
|
||||||
| lsp::CompletionItemKind::INTERFACE
|
| lsp::CompletionItemKind::INTERFACE
|
||||||
| lsp::CompletionItemKind::ENUM => Some("type"),
|
| lsp::CompletionItemKind::ENUM => Some("type"),
|
||||||
|
@ -433,27 +416,32 @@ impl LspAdapter for RustLspAdapter {
|
||||||
Some("constant")
|
Some("constant")
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
});
|
||||||
|
|
||||||
let mut label = completion.label.clone();
|
let label = completion.label.clone();
|
||||||
if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) {
|
let mut runs = vec![];
|
||||||
label.push(' ');
|
|
||||||
label.push_str(detail);
|
|
||||||
}
|
|
||||||
let mut label = CodeLabel::plain(label, completion.filter_text.as_deref());
|
|
||||||
if let Some(highlight_name) = highlight_name {
|
if let Some(highlight_name) = highlight_name {
|
||||||
let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?;
|
let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?;
|
||||||
label.runs.push((
|
runs.push((
|
||||||
0..label.text.rfind('(').unwrap_or(completion.label.len()),
|
0..label.rfind('(').unwrap_or(completion.label.len()),
|
||||||
highlight_id,
|
highlight_id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
mk_label(label, runs)
|
||||||
return Some(label);
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(detail_left) = detail_left {
|
||||||
|
label.text.push(' ');
|
||||||
|
if !detail_left.starts_with('(') {
|
||||||
|
label.text.push('(');
|
||||||
|
}
|
||||||
|
label.text.push_str(detail_left);
|
||||||
|
if !detail_left.ends_with(')') {
|
||||||
|
label.text.push(')');
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
None
|
Some(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn label_for_symbol(
|
async fn label_for_symbol(
|
||||||
|
@ -1169,7 +1157,7 @@ mod tests {
|
||||||
.await,
|
.await,
|
||||||
Some(CodeLabel {
|
Some(CodeLabel {
|
||||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||||
filter_range: 0..5,
|
filter_range: 0..10,
|
||||||
runs: vec![
|
runs: vec![
|
||||||
(0..5, highlight_function),
|
(0..5, highlight_function),
|
||||||
(7..10, highlight_keyword),
|
(7..10, highlight_keyword),
|
||||||
|
@ -1187,7 +1175,7 @@ mod tests {
|
||||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||||
label: "hello(…)".to_string(),
|
label: "hello(…)".to_string(),
|
||||||
label_details: Some(CompletionItemLabelDetails {
|
label_details: Some(CompletionItemLabelDetails {
|
||||||
detail: Some(" (use crate::foo)".into()),
|
detail: Some("(use crate::foo)".into()),
|
||||||
description: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
|
description: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -1197,7 +1185,7 @@ mod tests {
|
||||||
.await,
|
.await,
|
||||||
Some(CodeLabel {
|
Some(CodeLabel {
|
||||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||||
filter_range: 0..5,
|
filter_range: 0..10,
|
||||||
runs: vec![
|
runs: vec![
|
||||||
(0..5, highlight_function),
|
(0..5, highlight_function),
|
||||||
(7..10, highlight_keyword),
|
(7..10, highlight_keyword),
|
||||||
|
@ -1234,7 +1222,7 @@ mod tests {
|
||||||
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||||
label: "hello(…)".to_string(),
|
label: "hello(…)".to_string(),
|
||||||
label_details: Some(CompletionItemLabelDetails {
|
label_details: Some(CompletionItemLabelDetails {
|
||||||
detail: Some(" (use crate::foo)".to_string()),
|
detail: Some("(use crate::foo)".to_string()),
|
||||||
description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -1243,6 +1231,35 @@ mod tests {
|
||||||
&language
|
&language
|
||||||
)
|
)
|
||||||
.await,
|
.await,
|
||||||
|
Some(CodeLabel {
|
||||||
|
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||||
|
filter_range: 0..10,
|
||||||
|
runs: vec![
|
||||||
|
(0..5, highlight_function),
|
||||||
|
(7..10, highlight_keyword),
|
||||||
|
(11..17, highlight_type),
|
||||||
|
(18..19, highlight_type),
|
||||||
|
(25..28, highlight_type),
|
||||||
|
(29..30, highlight_type),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
adapter
|
||||||
|
.label_for_completion(
|
||||||
|
&lsp::CompletionItem {
|
||||||
|
kind: Some(lsp::CompletionItemKind::FUNCTION),
|
||||||
|
label: "hello".to_string(),
|
||||||
|
label_details: Some(CompletionItemLabelDetails {
|
||||||
|
detail: Some("(use crate::foo)".to_string()),
|
||||||
|
description: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&language
|
||||||
|
)
|
||||||
|
.await,
|
||||||
Some(CodeLabel {
|
Some(CodeLabel {
|
||||||
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
text: "hello(&mut Option<T>) -> Vec<T> (use crate::foo)".to_string(),
|
||||||
filter_range: 0..5,
|
filter_range: 0..5,
|
||||||
|
@ -1274,9 +1291,14 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await,
|
.await,
|
||||||
Some(CodeLabel {
|
Some(CodeLabel {
|
||||||
text: "await.as_deref_mut()".to_string(),
|
text: "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(),
|
||||||
filter_range: 6..18,
|
filter_range: 6..18,
|
||||||
runs: vec![],
|
runs: vec![
|
||||||
|
(6..18, HighlightId(2)),
|
||||||
|
(20..23, HighlightId(1)),
|
||||||
|
(33..40, HighlightId(0)),
|
||||||
|
(45..46, HighlightId(0))
|
||||||
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue