elixir: Improve ElixirLS LSP autocomplete to show labelDetails information (#21666)

João Otávio Biondo created

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

Release Notes:

- Improved ElixirLS LSP autocomplete to show module, function and struct
field details

![image](https://github.com/user-attachments/assets/2f05183f-8f7f-42c3-ba14-28fc58522488)

![image](https://github.com/user-attachments/assets/bfdea373-79ec-4dec-a9c7-5d15ad9403ee)

![image](https://github.com/user-attachments/assets/c0fd66d5-0e01-4e1e-a2d5-0a78d38e0b72)

Change summary

Cargo.lock                                          | 13 ++
extensions/elixir/Cargo.toml                        |  2 
extensions/elixir/src/language_servers/elixir_ls.rs | 75 ++++++++++++--
3 files changed, 75 insertions(+), 15 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -16139,7 +16139,7 @@ dependencies = [
 name = "zed_elixir"
 version = "0.1.1"
 dependencies = [
- "zed_extension_api 0.1.0",
+ "zed_extension_api 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -16183,6 +16183,17 @@ dependencies = [
  "wit-bindgen",
 ]
 
+[[package]]
+name = "zed_extension_api"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fd16b8b30a9dc920fc1678ff852f696b5bdf5b5843bc745a128be0aac29859e"
+dependencies = [
+ "serde",
+ "serde_json",
+ "wit-bindgen",
+]
+
 [[package]]
 name = "zed_glsl"
 version = "0.1.0"

extensions/elixir/Cargo.toml 🔗

@@ -13,4 +13,4 @@ path = "src/elixir.rs"
 crate-type = ["cdylib"]
 
 [dependencies]
-zed_extension_api = "0.1.0"
+zed_extension_api = "0.2.0"

extensions/elixir/src/language_servers/elixir_ls.rs 🔗

@@ -107,36 +107,85 @@ impl ElixirLs {
     }
 
     pub fn label_for_completion(&self, completion: Completion) -> Option<CodeLabel> {
+        let name = &completion.label;
+        let detail = completion
+            .detail
+            .filter(|detail| detail != "alias")
+            .map(|detail| format!(": {detail}"))
+            .unwrap_or("".to_string());
+
+        let detail_span = CodeLabelSpan::literal(detail, Some("comment.unused".to_string()));
+
         match completion.kind? {
-            CompletionKind::Module
-            | CompletionKind::Class
-            | CompletionKind::Interface
-            | CompletionKind::Struct => {
-                let name = completion.label;
+            CompletionKind::Module | CompletionKind::Class | CompletionKind::Struct => {
                 let defmodule = "defmodule ";
-                let code = format!("{defmodule}{name}");
+                let alias = completion
+                    .label_details
+                    .and_then(|details| details.description)
+                    .filter(|description| description.starts_with("alias"))
+                    .map(|description| format!(" ({description})"))
+                    .unwrap_or("".to_string());
+
+                let code = format!("{defmodule}{name}{alias}");
+                let name_start = defmodule.len();
+                let name_end = name_start + name.len();
 
                 Some(CodeLabel {
                     code,
-                    spans: vec![CodeLabelSpan::code_range(
-                        defmodule.len()..defmodule.len() + name.len(),
-                    )],
+                    spans: vec![
+                        CodeLabelSpan::code_range(name_start..name_end),
+                        detail_span,
+                        CodeLabelSpan::code_range(name_end..(name_end + alias.len())),
+                    ],
                     filter_range: (0..name.len()).into(),
                 })
             }
+            CompletionKind::Interface => Some(CodeLabel {
+                code: name.to_string(),
+                spans: vec![CodeLabelSpan::code_range(0..name.len()), detail_span],
+                filter_range: (0..name.len()).into(),
+            }),
+            CompletionKind::Field => Some(CodeLabel {
+                code: name.to_string(),
+                spans: vec![
+                    CodeLabelSpan::literal(name, Some("function".to_string())),
+                    detail_span,
+                ],
+                filter_range: (0..name.len()).into(),
+            }),
             CompletionKind::Function | CompletionKind::Constant => {
-                let name = completion.label;
+                let detail = completion
+                    .label_details
+                    .clone()
+                    .and_then(|details| details.detail)
+                    .unwrap_or("".to_string());
+
+                let description = completion
+                    .label_details
+                    .clone()
+                    .and_then(|details| details.description)
+                    .map(|description| format!(" ({description})"))
+                    .unwrap_or("".to_string());
+
                 let def = "def ";
-                let code = format!("{def}{name}");
+                let code = format!("{def}{name}{detail}{description}");
+
+                let name_start = def.len();
+                let name_end = name_start + name.len();
+                let detail_end = name_end + detail.len();
+                let description_end = detail_end + description.len();
 
                 Some(CodeLabel {
                     code,
-                    spans: vec![CodeLabelSpan::code_range(def.len()..def.len() + name.len())],
+                    spans: vec![
+                        CodeLabelSpan::code_range(name_start..name_end),
+                        CodeLabelSpan::code_range(name_end..detail_end),
+                        CodeLabelSpan::code_range(detail_end..description_end),
+                    ],
                     filter_range: (0..name.len()).into(),
                 })
             }
             CompletionKind::Operator => {
-                let name = completion.label;
                 let def_a = "def a ";
                 let code = format!("{def_a}{name} b");