diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 6545bf64a2..7fb6f44a5b 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -305,66 +305,63 @@ impl LspAdapter for RustLspAdapter { completion: &lsp::CompletionItem, language: &Arc, ) -> Option { - 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 .as_ref() - .and_then(|detail| detail.detail.as_ref()) + .and_then(|detail| detail.description.as_ref()) .or(completion.detail.as_ref()) .map(|detail| detail.trim()); - let function_signature = completion + // this tends to contain alias and import information + let detail_left = completion .label_details .as_ref() - .and_then(|detail| detail.description.as_deref()) - .or(completion.detail.as_deref()); - match (detail, completion.kind) { - (Some(detail), Some(lsp::CompletionItemKind::FIELD)) => { + .and_then(|detail| detail.detail.as_deref()); + let mk_label = |text: String, runs| { + let filter_range = completion + .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 text = format!("{name}: {detail}"); + let text = format!("{name}: {signature}"); let prefix = "struct S { "; let source = Rope::from(format!("{prefix}{text} }}")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); - let filter_range = completion - .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, - }); + mk_label(text, runs) } ( - Some(detail), + Some(signature), Some(lsp::CompletionItemKind::CONSTANT | lsp::CompletionItemKind::VARIABLE), ) if completion.insert_text_format != Some(lsp::InsertTextFormat::SNIPPET) => { let name = &completion.label; - let text = format!( - "{}: {}", - name, - completion.detail.as_deref().unwrap_or(detail) - ); + let text = format!("{name}: {signature}",); let prefix = "let "; let source = Rope::from(format!("{prefix}{text} = ();")); let runs = language.highlight_text(&source, prefix.len()..prefix.len() + text.len()); - let filter_range = completion - .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, - }); + mk_label(text, runs) } ( - Some(detail), + function_signature, Some(lsp::CompletionItemKind::FUNCTION | lsp::CompletionItemKind::METHOD), ) => { - static REGEX: LazyLock = LazyLock::new(|| Regex::new("\\(…?\\)").unwrap()); const FUNCTION_PREFIXES: [&str; 6] = [ "async fn", "async unsafe fn", @@ -373,34 +370,27 @@ impl LspAdapter for RustLspAdapter { "unsafe fn", "fn", ]; - // Is it function `async`? - let fn_keyword = FUNCTION_PREFIXES.iter().find_map(|prefix| { - function_signature.as_ref().and_then(|signature| { - signature - .strip_prefix(*prefix) - .map(|suffix| (*prefix, suffix)) - }) + let fn_prefixed = FUNCTION_PREFIXES.iter().find_map(|&prefix| { + function_signature? + .strip_prefix(prefix) + .map(|suffix| (prefix, suffix)) }); // fn keyword should be followed by opening parenthesis. - if let Some((prefix, suffix)) = fn_keyword { - let mut text = REGEX.replace(&completion.label, suffix).to_string(); + if let Some((prefix, suffix)) = fn_prefixed { + 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 run_start = prefix.len() + 1; let runs = language.highlight_text(&source, run_start..run_start + text.len()); - if detail.starts_with("(") { - 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, - }); + mk_label(text, runs) } else if completion .detail .as_ref() @@ -410,20 +400,13 @@ impl LspAdapter for RustLspAdapter { let len = text.len(); let source = Rope::from(text.as_str()); let runs = language.highlight_text(&source, 0..len); - let filter_range = completion - .filter_text - .as_deref() - .and_then(|filter| text.find(filter).map(|ix| ix..ix + filter.len())) - .unwrap_or(0..len); - return Some(CodeLabel { - filter_range, - text, - runs, - }); + mk_label(text, runs) + } else { + mk_label(completion.label.clone(), vec![]) } } - (_, Some(kind)) => { - let highlight_name = match kind { + (_, kind) => { + let highlight_name = kind.and_then(|kind| match kind { lsp::CompletionItemKind::STRUCT | lsp::CompletionItemKind::INTERFACE | lsp::CompletionItemKind::ENUM => Some("type"), @@ -433,27 +416,32 @@ impl LspAdapter for RustLspAdapter { Some("constant") } _ => None, - }; + }); - let mut label = completion.label.clone(); - if let Some(detail) = detail.filter(|detail| detail.starts_with("(")) { - label.push(' '); - label.push_str(detail); - } - let mut label = CodeLabel::plain(label, completion.filter_text.as_deref()); + let label = completion.label.clone(); + let mut runs = vec![]; if let Some(highlight_name) = highlight_name { let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name)?; - label.runs.push(( - 0..label.text.rfind('(').unwrap_or(completion.label.len()), + runs.push(( + 0..label.rfind('(').unwrap_or(completion.label.len()), highlight_id, )); } - - return Some(label); + mk_label(label, runs) + } + }; + + 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( @@ -1169,7 +1157,7 @@ mod tests { .await, Some(CodeLabel { text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, + filter_range: 0..10, runs: vec![ (0..5, highlight_function), (7..10, highlight_keyword), @@ -1187,7 +1175,7 @@ mod tests { kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), label_details: Some(CompletionItemLabelDetails { - detail: Some(" (use crate::foo)".into()), + detail: Some("(use crate::foo)".into()), description: Some("async fn(&mut Option) -> Vec".to_string()), }), ..Default::default() @@ -1197,7 +1185,7 @@ mod tests { .await, Some(CodeLabel { text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), - filter_range: 0..5, + filter_range: 0..10, runs: vec![ (0..5, highlight_function), (7..10, highlight_keyword), @@ -1234,7 +1222,7 @@ mod tests { kind: Some(lsp::CompletionItemKind::FUNCTION), label: "hello(…)".to_string(), label_details: Some(CompletionItemLabelDetails { - detail: Some(" (use crate::foo)".to_string()), + detail: Some("(use crate::foo)".to_string()), description: Some("fn(&mut Option) -> Vec".to_string()), }), @@ -1243,6 +1231,35 @@ mod tests { &language ) .await, + Some(CodeLabel { + text: "hello(&mut Option) -> Vec (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) -> Vec".to_string()), + }), + ..Default::default() + }, + &language + ) + .await, Some(CodeLabel { text: "hello(&mut Option) -> Vec (use crate::foo)".to_string(), filter_range: 0..5, @@ -1274,9 +1291,14 @@ mod tests { ) .await, Some(CodeLabel { - text: "await.as_deref_mut()".to_string(), + text: "await.as_deref_mut(&mut self) -> IterMut<'_, T>".to_string(), filter_range: 6..18, - runs: vec![], + runs: vec![ + (6..18, HighlightId(2)), + (20..23, HighlightId(1)), + (33..40, HighlightId(0)), + (45..46, HighlightId(0)) + ], }) );