Fix some issues found when testing Elixir-LS (#2583)

Max Brunsfeld created

Closes
https://linear.app/zed-industries/issue/Z-2209/popovers-dont-always-have-syntax-highlighted-code
Closes
https://linear.app/zed-industries/issue/Z-2206/highlight-syntax-in-hover-docs

* Fix a ton of errors in our logs due to us not recognizing that
`elixir-ls` does not support code actions.
* Syntax-highlight elixir code blocks in hover popovers

Change summary

crates/editor/src/hover_popover.rs | 43 +++++++++------------
crates/lsp/src/lsp.rs              |  7 ++-
crates/project/src/lsp_command.rs  | 62 ++++++++++++++++++++-----------
crates/project/src/project.rs      |  1 
4 files changed, 63 insertions(+), 50 deletions(-)

Detailed changes

crates/editor/src/hover_popover.rs 🔗

@@ -221,6 +221,7 @@ fn show_hover(
                     project: project.clone(),
                     symbol_range: range,
                     blocks: hover_result.contents,
+                    language: hover_result.language,
                     rendered_content: None,
                 })
             });
@@ -253,6 +254,7 @@ fn render_blocks(
     theme_id: usize,
     blocks: &[HoverBlock],
     language_registry: &Arc<LanguageRegistry>,
+    language: Option<&Arc<Language>>,
     style: &EditorStyle,
 ) -> RenderedInfo {
     let mut text = String::new();
@@ -351,11 +353,13 @@ fn render_blocks(
                             }
                             Tag::CodeBlock(kind) => {
                                 new_paragraph(&mut text, &mut list_stack);
-                                if let CodeBlockKind::Fenced(language) = kind {
-                                    current_language = language_registry
+                                current_language = if let CodeBlockKind::Fenced(language) = kind {
+                                    language_registry
                                         .language_for_name(language.as_ref())
                                         .now_or_never()
-                                        .and_then(Result::ok);
+                                        .and_then(Result::ok)
+                                } else {
+                                    language.cloned()
                                 }
                             }
                             Tag::Emphasis => italic_depth += 1,
@@ -414,10 +418,6 @@ fn render_blocks(
         }
     }
 
-    if !text.is_empty() && !text.ends_with('\n') {
-        text.push('\n');
-    }
-
     RenderedInfo {
         theme_id,
         text,
@@ -524,6 +524,7 @@ pub struct InfoPopover {
     pub project: ModelHandle<Project>,
     pub symbol_range: Range<Anchor>,
     pub blocks: Vec<HoverBlock>,
+    language: Option<Arc<Language>>,
     rendered_content: Option<RenderedInfo>,
 }
 
@@ -559,6 +560,7 @@ impl InfoPopover {
                 style.theme_id,
                 &self.blocks,
                 self.project.read(cx).languages(),
+                self.language.as_ref(),
                 style,
             )
         });
@@ -588,10 +590,7 @@ impl InfoPopover {
                                         MouseRegion::new::<Self>(view_id, region_id, bounds)
                                             .on_click::<Editor, _>(
                                                 MouseButton::Left,
-                                                move |_, _, cx| {
-                                                    println!("clicked link {url}");
-                                                    cx.platform().open_url(&url);
-                                                },
+                                                move |_, _, cx| cx.platform().open_url(&url),
                                             ),
                                     );
                                 }
@@ -906,7 +905,7 @@ mod tests {
                         text: "one **two** three".to_string(),
                         kind: HoverBlockKind::Markdown,
                     }],
-                    expected_marked_text: "one «two» three\n".to_string(),
+                    expected_marked_text: "one «two» three".to_string(),
                     expected_styles: vec![HighlightStyle {
                         weight: Some(Weight::BOLD),
                         ..Default::default()
@@ -918,7 +917,7 @@ mod tests {
                         text: "one [two](the-url) three".to_string(),
                         kind: HoverBlockKind::Markdown,
                     }],
-                    expected_marked_text: "one «two» three\n".to_string(),
+                    expected_marked_text: "one «two» three".to_string(),
                     expected_styles: vec![HighlightStyle {
                         underline: Some(Underline {
                             thickness: 1.0.into(),
@@ -937,8 +936,7 @@ mod tests {
                                 - b
                             * two
                                 - [c](the-url)
-                                - d
-                        "
+                                - d"
                         .unindent(),
                         kind: HoverBlockKind::Markdown,
                     }],
@@ -949,8 +947,7 @@ mod tests {
                           - b
                         - two
                           - «c»
-                          - d
-                    "
+                          - d"
                     .unindent(),
                     expected_styles: vec![HighlightStyle {
                         underline: Some(Underline {
@@ -973,9 +970,8 @@ mod tests {
 
                                   nine
                                 * ten
-                            * six
-                        "
-                        .unindent(),
+                            * six"
+                            .unindent(),
                         kind: HoverBlockKind::Markdown,
                     }],
                     expected_marked_text: "
@@ -985,9 +981,8 @@ mod tests {
 
                             nine
                           - ten
-                        - six
-                    "
-                    .unindent(),
+                        - six"
+                        .unindent(),
                     expected_styles: vec![HighlightStyle {
                         underline: Some(Underline {
                             thickness: 1.0.into(),
@@ -1004,7 +999,7 @@ mod tests {
                 expected_styles,
             } in &rows[0..]
             {
-                let rendered = render_blocks(0, &blocks, &Default::default(), &style);
+                let rendered = render_blocks(0, &blocks, &Default::default(), None, &style);
 
                 let (expected_text, ranges) = marked_text_ranges(expected_marked_text, false);
                 let expected_highlights = ranges

crates/lsp/src/lsp.rs 🔗

@@ -260,9 +260,10 @@ impl LanguageServer {
             buffer.clear();
             stdout.read_until(b'\n', &mut buffer).await?;
             stdout.read_until(b'\n', &mut buffer).await?;
-            let message_len: usize = std::str::from_utf8(&buffer)?
+            let header = std::str::from_utf8(&buffer)?;
+            let message_len: usize = header
                 .strip_prefix(CONTENT_LEN_HEADER)
-                .ok_or_else(|| anyhow!("invalid header"))?
+                .ok_or_else(|| anyhow!("invalid LSP message header {header:?}"))?
                 .trim_end()
                 .parse()?;
 
@@ -301,7 +302,7 @@ impl LanguageServer {
                 }
             } else {
                 warn!(
-                    "Failed to deserialize message:\n{}",
+                    "failed to deserialize LSP message:\n{}",
                     std::str::from_utf8(&buffer)?
                 );
             }

crates/project/src/lsp_command.rs 🔗

@@ -1111,14 +1111,18 @@ impl LspCommand for GetHover {
         cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         Ok(message.and_then(|hover| {
-            let range = hover.range.map(|range| {
-                cx.read(|cx| {
-                    let buffer = buffer.read(cx);
-                    let token_start =
-                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
-                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
-                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
-                })
+            let (language, range) = cx.read(|cx| {
+                let buffer = buffer.read(cx);
+                (
+                    buffer.language().cloned(),
+                    hover.range.map(|range| {
+                        let token_start =
+                            buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
+                        let token_end =
+                            buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
+                        buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
+                    }),
+                )
             });
 
             fn hover_blocks_from_marked_string(
@@ -1163,7 +1167,11 @@ impl LspCommand for GetHover {
                 }],
             });
 
-            Some(Hover { contents, range })
+            Some(Hover {
+                contents,
+                range,
+                language,
+            })
         }))
     }
 
@@ -1247,16 +1255,9 @@ impl LspCommand for GetHover {
         self,
         message: proto::GetHoverResponse,
         _: ModelHandle<Project>,
-        _: ModelHandle<Buffer>,
-        _: AsyncAppContext,
+        buffer: ModelHandle<Buffer>,
+        cx: AsyncAppContext,
     ) -> Result<Self::Response> {
-        let range = if let (Some(start), Some(end)) = (message.start, message.end) {
-            language::proto::deserialize_anchor(start)
-                .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
-        } else {
-            None
-        };
-
         let contents: Vec<_> = message
             .contents
             .into_iter()
@@ -1271,12 +1272,23 @@ impl LspCommand for GetHover {
                 },
             })
             .collect();
+        if contents.is_empty() {
+            return Ok(None);
+        }
 
-        Ok(if contents.is_empty() {
-            None
+        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned());
+        let range = if let (Some(start), Some(end)) = (message.start, message.end) {
+            language::proto::deserialize_anchor(start)
+                .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
         } else {
-            Some(Hover { contents, range })
-        })
+            None
+        };
+
+        Ok(Some(Hover {
+            contents,
+            range,
+            language,
+        }))
     }
 
     fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64 {
@@ -1499,7 +1511,11 @@ impl LspCommand for GetCodeActions {
     type ProtoRequest = proto::GetCodeActions;
 
     fn check_capabilities(&self, capabilities: &ServerCapabilities) -> bool {
-        capabilities.code_action_provider.is_some()
+        match &capabilities.code_action_provider {
+            None => false,
+            Some(lsp::CodeActionProviderCapability::Simple(false)) => false,
+            _ => true,
+        }
     }
 
     fn to_lsp(

crates/project/src/project.rs 🔗

@@ -359,6 +359,7 @@ pub enum HoverBlockKind {
 pub struct Hover {
     pub contents: Vec<HoverBlock>,
     pub range: Option<Range<language::Anchor>>,
+    pub language: Option<Arc<Language>>,
 }
 
 #[derive(Default)]