rust: Highlight async functions in completions (#3095)

Piotr Osiewicz created

Before (code in screenshot is from this branch,
`crates/zed/languages/rust.rs:179`):

![image](https://github.com/zed-industries/zed/assets/24362066/6b709f8c-1b80-4aaa-8ddc-8db9dbca5a5e)
Notice how the last 2 entries (that are async functions) are not
highlighted properly.
After:

![image](https://github.com/zed-industries/zed/assets/24362066/88337f43-b97f-4257-9c31-54c9023e8dbb)

This is slightly suboptimal, as it's hard to tell that this is an async
function - I guess adding an `async` prefix is not really an option, as
then we should have a prefix for non-async functions too. Still, at
least you can tell that something is a function in the first place. :)

Release Notes:
- Fixed Rust async functions not being highlighted in completions.

Change summary

crates/zed/src/languages/rust.rs | 51 ++++++++++++++++++++++++++-------
1 file changed, 40 insertions(+), 11 deletions(-)

Detailed changes

crates/zed/src/languages/rust.rs 🔗

@@ -165,17 +165,25 @@ impl LspAdapter for RustLspAdapter {
                 lazy_static! {
                     static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap();
                 }
-
                 let detail = completion.detail.as_ref().unwrap();
-                if detail.starts_with("fn(") {
-                    let text = REGEX.replace(&completion.label, &detail[2..]).to_string();
-                    let source = Rope::from(format!("fn {} {{}}", text).as_str());
-                    let runs = language.highlight_text(&source, 3..3 + text.len());
-                    return Some(CodeLabel {
-                        filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
-                        text,
-                        runs,
-                    });
+                const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"];
+                let prefix = FUNCTION_PREFIXES
+                    .iter()
+                    .find_map(|prefix| detail.strip_prefix(*prefix).map(|suffix| (prefix, suffix)));
+                // fn keyword should be followed by opening parenthesis.
+                if let Some((prefix, suffix)) = prefix {
+                    if suffix.starts_with('(') {
+                        let text = REGEX.replace(&completion.label, suffix).to_string();
+                        let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());
+                        let run_start = prefix.len() + 1;
+                        let runs =
+                            language.highlight_text(&source, run_start..run_start + text.len());
+                        return Some(CodeLabel {
+                            filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
+                            text,
+                            runs,
+                        });
+                    }
                 }
             }
             Some(kind) => {
@@ -377,7 +385,28 @@ mod tests {
                 ],
             })
         );
-
+        assert_eq!(
+            language
+                .label_for_completion(&lsp::CompletionItem {
+                    kind: Some(lsp::CompletionItemKind::FUNCTION),
+                    label: "hello(…)".to_string(),
+                    detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
+                    ..Default::default()
+                })
+                .await,
+            Some(CodeLabel {
+                text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+                filter_range: 0..5,
+                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!(
             language
                 .label_for_completion(&lsp::CompletionItem {