editor: Improve snippet completion to show key inline in completion and description as aside (#30603)

Smit Barmase created

Closes #28028

Before:
<img width="742" alt="image"
src="https://github.com/user-attachments/assets/31723970-5420-40ea-a394-4ffa0038925c"
/>

After:
<img width="989" alt="image"
src="https://github.com/user-attachments/assets/0aebc317-a234-4e68-8304-cb479513af15"
/>


Release Notes:

- Improved snippet code completion to show key in completion menu and
description in aside.

Change summary

crates/editor/src/code_context_menus.rs | 42 +++++++++++++++++---------
crates/editor/src/editor.rs             | 12 +++++-
crates/project/src/lsp_store.rs         |  5 +++
crates/snippet_provider/src/lib.rs      |  7 +++-
4 files changed, 46 insertions(+), 20 deletions(-)

Detailed changes

crates/editor/src/code_context_menus.rs 🔗

@@ -510,22 +510,25 @@ impl CompletionsMenu {
 
                         let completion_label = StyledText::new(completion.label.text.clone())
                             .with_default_highlights(&style.text, highlights);
-                        let documentation_label = if let Some(
-                            CompletionDocumentation::SingleLine(text),
-                        ) = documentation
-                        {
-                            if text.trim().is_empty() {
-                                None
-                            } else {
-                                Some(
-                                    Label::new(text.clone())
-                                        .ml_4()
-                                        .size(LabelSize::Small)
-                                        .color(Color::Muted),
-                                )
+
+                        let documentation_label = match documentation {
+                            Some(CompletionDocumentation::SingleLine(text))
+                            | Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
+                                single_line: text,
+                                ..
+                            }) => {
+                                if text.trim().is_empty() {
+                                    None
+                                } else {
+                                    Some(
+                                        Label::new(text.clone())
+                                            .ml_4()
+                                            .size(LabelSize::Small)
+                                            .color(Color::Muted),
+                                    )
+                                }
                             }
-                        } else {
-                            None
+                            _ => None,
                         };
 
                         let start_slot = completion
@@ -597,6 +600,10 @@ impl CompletionsMenu {
             .as_ref()?
         {
             CompletionDocumentation::MultiLinePlainText(text) => div().child(text.clone()),
+            CompletionDocumentation::SingleLineAndMultiLinePlainText {
+                plain_text: Some(text),
+                ..
+            } => div().child(text.clone()),
             CompletionDocumentation::MultiLineMarkdown(parsed) if !parsed.is_empty() => {
                 let markdown = self.markdown_element.get_or_insert_with(|| {
                     cx.new(|cx| {
@@ -627,6 +634,11 @@ impl CompletionsMenu {
             CompletionDocumentation::MultiLineMarkdown(_) => return None,
             CompletionDocumentation::SingleLine(_) => return None,
             CompletionDocumentation::Undocumented => return None,
+            CompletionDocumentation::SingleLineAndMultiLinePlainText {
+                plain_text: None, ..
+            } => {
+                return None;
+            }
         };
 
         Some(

crates/editor/src/editor.rs 🔗

@@ -19872,9 +19872,15 @@ fn snippet_completions(
                             filter_range: 0..matching_prefix.len(),
                         },
                         icon_path: None,
-                        documentation: snippet.description.clone().map(|description| {
-                            CompletionDocumentation::SingleLine(description.into())
-                        }),
+                        documentation: Some(
+                            CompletionDocumentation::SingleLineAndMultiLinePlainText {
+                                single_line: snippet.name.clone().into(),
+                                plain_text: snippet
+                                    .description
+                                    .clone()
+                                    .map(|description| description.into()),
+                            },
+                        ),
                         insert_text_mode: None,
                         confirm: None,
                     })

crates/project/src/lsp_store.rs 🔗

@@ -9897,6 +9897,11 @@ pub enum CompletionDocumentation {
     MultiLinePlainText(SharedString),
     /// Markdown documentation.
     MultiLineMarkdown(SharedString),
+    /// Both single line and multiple lines of plain text documentation.
+    SingleLineAndMultiLinePlainText {
+        single_line: SharedString,
+        plain_text: Option<SharedString>,
+    },
 }
 
 impl From<lsp::Documentation> for CompletionDocumentation {

crates/snippet_provider/src/lib.rs 🔗

@@ -34,10 +34,11 @@ fn file_stem_to_key(stem: &str) -> SnippetKind {
 
 fn file_to_snippets(file_contents: VsSnippetsFile) -> Vec<Arc<Snippet>> {
     let mut snippets = vec![];
-    for (prefix, snippet) in file_contents.snippets {
+    for (name, snippet) in file_contents.snippets {
+        let snippet_name = name.clone();
         let prefixes = snippet
             .prefix
-            .map_or_else(move || vec![prefix], |prefixes| prefixes.into());
+            .map_or_else(move || vec![snippet_name], |prefixes| prefixes.into());
         let description = snippet
             .description
             .map(|description| description.to_string());
@@ -49,6 +50,7 @@ fn file_to_snippets(file_contents: VsSnippetsFile) -> Vec<Arc<Snippet>> {
             body,
             prefix: prefixes,
             description,
+            name,
         }));
     }
     snippets
@@ -59,6 +61,7 @@ pub struct Snippet {
     pub prefix: Vec<String>,
     pub body: String,
     pub description: Option<String>,
+    pub name: String,
 }
 
 async fn process_updates(