New plan, call hover_docs on the type where the inlay hint points to

David Kleingeld and Richard Feldman created

Co-authored-by: Richard Feldman <oss@rtfeldman.com>

Change summary

crates/editor/src/hover_links.rs | 168 +++++++++++----------------------
1 file changed, 57 insertions(+), 111 deletions(-)

Detailed changes

crates/editor/src/hover_links.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
     Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
     GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
-    display_map::InlayOffset,
     editor_settings::GoToDefinitionFallback,
     hover_popover::{self, InlayHover},
     scroll::ScrollAmount,
@@ -16,7 +15,7 @@ use project::{
 };
 use settings::Settings;
 use std::ops::Range;
-use text;
+use text::{self, Point};
 use theme::ActiveTheme as _;
 use util::{ResultExt, TryFutureExt as _, maybe};
 
@@ -445,8 +444,8 @@ pub fn update_inlay_link_and_hover_points(
                                             let location = location.clone();
 
                                             get_docs_then_show_hover(
-                                                window, cx, highlight, hint_value, location,
-                                                project,
+                                                editor, window, cx, highlight, hint_value,
+                                                location, project,
                                             );
                                         }
 
@@ -486,7 +485,20 @@ pub fn update_inlay_link_and_hover_points(
     }
 }
 
+/// todo dvdsk: This extracts doc comments and shows them, a fine fallback
+/// though maybe we should instead alsk the LSP for info on the type?. Lets figure
+/// out what normal hoverdocs do.
+///
+/// issue with Entity<Markdown>. Needs one more (expensive) step before rendering
+/// which we need to cache.
+///
+/// --- PLAN ---
+/// - use location to call hover_popover and extract the text
+/// - then use claude written 'existing' hover method to see if that works
+/// - try use or adapt InfoPopover to get scrollbar/nice rendering (note issue above)
+/// - get the unwraps and as_refs out of here
 fn get_docs_then_show_hover(
+    editor: &mut Editor,
     window: &mut Window,
     cx: &mut Context<'_, Editor>,
     highlight: InlayHighlight,
@@ -494,128 +506,62 @@ fn get_docs_then_show_hover(
     location: lsp::Location,
     project: Entity<Project>,
 ) {
+    let provider = editor.semantics_provider.clone().unwrap();
     cx.spawn_in(window, async move |editor, cx| {
         async move {
-            // Small delay to show the loading message first
-            cx.background_executor()
-                .timer(std::time::Duration::from_millis(50))
-                .await;
-
             // Convert LSP URL to file path
             let file_path = location
                 .uri
                 .to_file_path()
                 .map_err(|_| anyhow::anyhow!("Invalid file URL"))?;
-
             // Open the definition file
             let definition_buffer = project
                 .update(cx, |project, cx| project.open_local_buffer(file_path, cx))?
                 .await?;
+            let location = Point::new(location.range.start.line, location.range.start.character);
+            let buffer_position = definition_buffer
+                .update(cx, |buffer, _| buffer.snapshot().anchor_after(location))
+                .unwrap();
 
-            // Extract documentation directly from the source
-            let documentation = definition_buffer.update(cx, |buffer, _| {
-                let line_number = location.range.start.line as usize;
-
-                // Get the text of the buffer
-                let text = buffer.text();
-                let lines: Vec<&str> = text.lines().collect();
-
-                // Look backwards from the definition line to find doc comments
-                let mut doc_lines = Vec::new();
-                let mut current_line = line_number.saturating_sub(1);
-
-                // Skip any attributes like #[derive(...)]
-                while current_line > 0
-                    && lines.get(current_line).map_or(false, |line| {
-                        let trimmed = line.trim();
-                        trimmed.starts_with("#[") || trimmed.is_empty()
-                    })
-                {
-                    current_line = current_line.saturating_sub(1);
-                }
-
-                // Collect doc comments
-                while current_line > 0 {
-                    if let Some(line) = lines.get(current_line) {
-                        let trimmed = line.trim();
-                        if trimmed.starts_with("///") {
-                            // Remove the /// and any leading space
-                            let doc_text = trimmed
-                                .strip_prefix("///")
-                                .unwrap_or("")
-                                .strip_prefix(" ")
-                                .unwrap_or_else(|| trimmed.strip_prefix("///").unwrap_or(""));
-                            doc_lines.push(doc_text.to_string());
-                        } else if !trimmed.is_empty() {
-                            // Stop at the first non-doc, non-empty line
-                            break;
-                        }
-                    }
-                    current_line = current_line.saturating_sub(1);
-                }
-
-                // Reverse to get correct order
-                doc_lines.reverse();
-
-                // Also get the actual definition line
-                let definition = lines
-                    .get(line_number)
-                    .map(|s| s.trim().to_string())
-                    .unwrap_or_else(|| hint_value.clone());
-
-                if doc_lines.is_empty() {
-                    None
-                } else {
-                    let docs = doc_lines.join("\n");
-                    Some((definition, docs))
-                }
-            })?;
+            // debounce the lsp request
+            cx.background_executor()
+                .timer(std::time::Duration::from_millis(50))
+                .await;
+            let hover_request =
+                cx.update(|_, cx| provider.hover(&definition_buffer, buffer_position, cx))?;
+            let hovers_response = if let Some(hover_request) = hover_request {
+                hover_request.await.unwrap_or_default()
+            } else {
+                Vec::new()
+            };
 
-            if let Some((definition, docs)) = documentation {
-                // Format as markdown with the definition as a code block
-                let formatted_docs = format!("```rust\n{}\n```\n\n{}", definition, docs);
+            let text = hovers_response
+                .first()
+                .as_ref()
+                .unwrap()
+                .contents
+                .first()
+                .as_ref()
+                .unwrap()
+                .text
+                .clone();
 
-                editor
-                    .update_in(cx, |editor, window, cx| {
-                        hover_popover::hover_at_inlay(
-                            editor,
-                            InlayHover {
-                                tooltip: HoverBlock {
-                                    text: formatted_docs,
-                                    kind: HoverBlockKind::Markdown,
-                                },
-                                range: highlight,
-                            },
-                            window,
-                            cx,
-                        );
-                    })
-                    .log_err();
-            } else {
-                // Fallback to showing just the location info
-                let fallback_text = format!(
-                    "{}\n\nDefined in at line {}",
-                    hint_value.trim(),
-                    // filename, // TODO
-                    location.range.start.line + 1
-                );
-                editor
-                    .update_in(cx, |editor, window, cx| {
-                        hover_popover::hover_at_inlay(
-                            editor,
-                            InlayHover {
-                                tooltip: HoverBlock {
-                                    text: fallback_text,
-                                    kind: HoverBlockKind::PlainText,
-                                },
-                                range: highlight,
+            editor
+                .update_in(cx, |editor, window, cx| {
+                    hover_popover::hover_at_inlay(
+                        editor,
+                        InlayHover {
+                            tooltip: HoverBlock {
+                                text,
+                                kind: HoverBlockKind::Markdown,
                             },
-                            window,
-                            cx,
-                        );
-                    })
-                    .log_err();
-            }
+                            range: highlight,
+                        },
+                        window,
+                        cx,
+                    );
+                })
+                .log_err();
 
             anyhow::Ok(())
         }