gleam: Strip newlines in completion details returned from language server (#10304)

Marshall Bowers created

This PR updates the Gleam extension to strip out newlines in the
completion details returned from the language server.

These newlines were causing the completion menu to compute a large
height for each item, resulting in lots of empty space in the completion
menu:

<img width="878" alt="Screenshot 2024-04-08 at 8 53 29 PM"
src="https://github.com/zed-industries/zed/assets/1486634/383c52ec-e5cb-4496-ae4c-28744b4ecaf5">

The approach to stripping newlines allocates a bit more than I would
like.

It would be good to see if it is possible for the Gleam language server
to not send us these newlines in the first place.

Release Notes:

- N/A

Change summary

extensions/gleam/src/gleam.rs | 39 ++++++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+), 1 deletion(-)

Detailed changes

extensions/gleam/src/gleam.rs 🔗

@@ -114,7 +114,7 @@ impl zed::Extension for GleamExtension {
         completion: zed::lsp::Completion,
     ) -> Option<zed::CodeLabel> {
         let name = &completion.label;
-        let ty = completion.detail?;
+        let ty = strip_newlines_from_detail(&completion.detail?);
         let let_binding = "let a";
         let colon = ": ";
         let assignment = " = ";
@@ -146,3 +146,40 @@ impl zed::Extension for GleamExtension {
 }
 
 zed::register_extension!(GleamExtension);
+
+/// Removes newlines from the completion detail.
+///
+/// The Gleam LSP can return types containing newlines, which causes formatting
+/// issues within the Zed completions menu.
+fn strip_newlines_from_detail(detail: &str) -> String {
+    let without_newlines = detail
+        .replace("->\n  ", "-> ")
+        .replace("\n  ", "")
+        .replace(",\n", "");
+
+    let comma_delimited_parts = without_newlines.split(',');
+    comma_delimited_parts
+        .map(|part| part.trim())
+        .collect::<Vec<_>>()
+        .join(", ")
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::strip_newlines_from_detail;
+
+    #[test]
+    fn test_strip_newlines_from_detail() {
+        let detail = "fn(\n  Selector(a),\n  b,\n  fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a,\n) -> Selector(a)";
+        let expected = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) -> Selector(a)";
+        assert_eq!(strip_newlines_from_detail(detail), expected);
+
+        let detail = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) ->\n  Selector(a)";
+        let expected = "fn(Selector(a), b, fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> a) -> Selector(a)";
+        assert_eq!(strip_newlines_from_detail(detail), expected);
+
+        let detail = "fn(\n  Method,\n  List(#(String, String)),\n  a,\n  Scheme,\n  String,\n  Option(Int),\n  String,\n  Option(String),\n) -> Request(a)";
+        let expected = "fn(Method, List(#(String, String)), a, Scheme, String, Option(Int), String, Option(String)) -> Request(a)";
+        assert_eq!(strip_newlines_from_detail(&detail), expected);
+    }
+}