Kinda make this work

Richard Feldman created

Change summary

crates/editor/src/hover_links.rs   | 457 ++++++++++++++++++-------------
crates/editor/src/hover_popover.rs |  12 
2 files changed, 269 insertions(+), 200 deletions(-)

Detailed changes

crates/editor/src/hover_links.rs 🔗

@@ -1,6 +1,7 @@
 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,
@@ -15,6 +16,7 @@ use project::{
 };
 use settings::Settings;
 use std::ops::Range;
+use text;
 use theme::ActiveTheme as _;
 use util::{ResultExt, TryFutureExt as _, maybe};
 
@@ -293,93 +295,45 @@ pub fn update_inlay_link_and_hover_points(
     window: &mut Window,
     cx: &mut Context<Editor>,
 ) {
-    eprintln!("update_inlay_link_and_hover_points called");
     // For inlay hints, we need to use the exact position where the mouse is
-    let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
-        // Use the exact unclipped position to get the correct offset within the inlay
-        Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
-    } else {
-        None
-    };
+    // But we must clip it to valid bounds to avoid panics
+    let clipped_point = snapshot.clip_point(point_for_position.exact_unclipped, Bias::Left);
+    let hovered_offset = snapshot.display_point_to_inlay_offset(clipped_point, Bias::Left);
+
     let mut go_to_definition_updated = false;
     let mut hover_updated = false;
-    if let Some(hovered_offset) = hovered_offset {
-        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
-        let previous_valid_anchor = buffer_snapshot.anchor_at(
-            point_for_position.previous_valid.to_point(snapshot),
-            Bias::Left,
-        );
-        let next_valid_anchor = buffer_snapshot.anchor_at(
-            point_for_position.next_valid.to_point(snapshot),
-            Bias::Right,
-        );
-        eprintln!(
-            "Looking for inlay hints between {:?} and {:?}",
-            previous_valid_anchor, next_valid_anchor
-        );
-        let visible_hints = editor.visible_inlay_hints(cx);
-        eprintln!("Total visible inlay hints: {}", visible_hints.len());
-        if let Some(hovered_hint) = visible_hints
-            .into_iter()
-            .skip_while(|hint| {
-                let cmp = hint
-                    .position
-                    .cmp(&previous_valid_anchor, &buffer_snapshot)
-                    .is_lt();
-                eprintln!(
-                    "Checking hint {:?} at {:?} < prev {:?}: {}",
-                    hint.id, hint.position, previous_valid_anchor, cmp
-                );
-                cmp
-            })
-            .take_while(|hint| {
-                let cmp = hint
-                    .position
-                    .cmp(&next_valid_anchor, &buffer_snapshot)
-                    .is_le();
-                eprintln!(
-                    "Checking hint {:?} at {:?} <= next {:?}: {}",
-                    hint.id, hint.position, next_valid_anchor, cmp
-                );
-                cmp
-            })
-            .max_by_key(|hint| hint.id)
-        {
-            eprintln!("Found hovered inlay hint: {:?}", hovered_hint.id);
-            let inlay_hint_cache = editor.inlay_hint_cache();
-            let excerpt_id = previous_valid_anchor.excerpt_id;
-            if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
-                eprintln!(
-                    "Cached hint state: {:?}, has tooltip: {}, label: {:?}",
-                    cached_hint.resolve_state,
-                    cached_hint.tooltip.is_some(),
-                    match &cached_hint.label {
-                        project::InlayHintLabel::String(s) => format!("String({})", s),
-                        project::InlayHintLabel::LabelParts(parts) =>
-                            format!("LabelParts({} parts)", parts.len()),
-                    }
-                );
 
-                // Debug full hint content
-                eprintln!("Full cached hint: {:?}", cached_hint);
-                if let project::InlayHintLabel::LabelParts(ref parts) = cached_hint.label {
-                    for (i, part) in parts.iter().enumerate() {
-                        eprintln!(
-                            "  Part {}: value='{}', tooltip={:?}, location={:?}",
-                            i, part.value, part.tooltip, part.location
-                        );
-                    }
-                }
+    // Get all visible inlay hints
+    let visible_hints = editor.visible_inlay_hints(cx);
+
+    // Find if we're hovering over an inlay hint
+    if let Some(hovered_inlay) = visible_hints.into_iter().find(|inlay| {
+        // Only process hint inlays
+        if !matches!(inlay.id, InlayId::Hint(_)) {
+            return false;
+        }
+
+        // Check if the hovered position falls within this inlay's display range
+        let inlay_start = snapshot.anchor_to_inlay_offset(inlay.position);
+        let inlay_end = InlayOffset(inlay_start.0 + inlay.text.len());
+
+        hovered_offset >= inlay_start && hovered_offset < inlay_end
+    }) {
+        let inlay_hint_cache = editor.inlay_hint_cache();
+        let excerpt_id = hovered_inlay.position.excerpt_id;
+
+        // Extract the hint ID from the inlay
+        if let InlayId::Hint(_hint_id) = hovered_inlay.id {
+            if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_inlay.id) {
                 // Check if we should process this hint for hover
                 let should_process_hint = match cached_hint.resolve_state {
                     ResolveState::CanResolve(_, _) => {
                         // For unresolved hints, spawn resolution
-                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-                            eprintln!("Spawning hint resolution for hint {:?}", hovered_hint.id);
+                        if let Some(buffer_id) = hovered_inlay.position.buffer_id {
                             inlay_hint_cache.spawn_hint_resolve(
                                 buffer_id,
                                 excerpt_id,
-                                hovered_hint.id,
+                                hovered_inlay.id,
                                 window,
                                 cx,
                             );
@@ -396,7 +350,6 @@ pub fn update_inlay_link_and_hover_points(
                     }
                 };
 
-                eprintln!("Should process hint: {}", should_process_hint);
                 if should_process_hint {
                     let mut extra_shift_left = 0;
                     let mut extra_shift_right = 0;
@@ -409,9 +362,7 @@ pub fn update_inlay_link_and_hover_points(
                     }
                     match cached_hint.label {
                         project::InlayHintLabel::String(_) => {
-                            eprintln!("Processing String label hint");
                             if let Some(tooltip) = cached_hint.tooltip {
-                                eprintln!("Found tooltip on main hint, calling hover_at_inlay");
                                 hover_popover::hover_at_inlay(
                                     editor,
                                     InlayHover {
@@ -428,10 +379,10 @@ pub fn update_inlay_link_and_hover_points(
                                             }
                                         },
                                         range: InlayHighlight {
-                                            inlay: hovered_hint.id,
-                                            inlay_position: hovered_hint.position,
+                                            inlay: hovered_inlay.id,
+                                            inlay_position: hovered_inlay.position,
                                             range: extra_shift_left
-                                                ..hovered_hint.text.len() + extra_shift_right,
+                                                ..hovered_inlay.text.len() + extra_shift_right,
                                         },
                                     },
                                     window,
@@ -441,133 +392,263 @@ pub fn update_inlay_link_and_hover_points(
                             }
                         }
                         project::InlayHintLabel::LabelParts(label_parts) => {
-                            eprintln!(
-                                "Processing LabelParts hint with {} parts",
-                                label_parts.len()
-                            );
-                            let hint_start = snapshot.anchor_to_inlay_offset(hovered_hint.position);
-                            eprintln!("Hint start offset: {:?}", hint_start);
-                            eprintln!("Hovered offset: {:?}", hovered_offset);
-                            if let Some((hovered_hint_part, part_range)) =
-                                hover_popover::find_hovered_hint_part(
-                                    label_parts.clone(),
-                                    hint_start,
-                                    hovered_offset,
-                                )
-                            {
-                                eprintln!("Found hovered hint part: {:?}", hovered_hint_part.value);
-                                eprintln!(
-                                    "Part has tooltip: {}",
-                                    hovered_hint_part.tooltip.is_some()
-                                );
-                                let highlight_start =
-                                    (part_range.start - hint_start).0 + extra_shift_left;
-                                let highlight_end =
-                                    (part_range.end - hint_start).0 + extra_shift_right;
-                                let highlight = InlayHighlight {
-                                    inlay: hovered_hint.id,
-                                    inlay_position: hovered_hint.position,
-                                    range: highlight_start..highlight_end,
-                                };
-                                if let Some(tooltip) = hovered_hint_part.tooltip {
-                                    eprintln!("Found tooltip on hint part, calling hover_at_inlay");
-                                    hover_popover::hover_at_inlay(
-                                        editor,
-                                        InlayHover {
-                                            tooltip: match tooltip {
-                                                InlayHintLabelPartTooltip::String(text) => {
-                                                    HoverBlock {
-                                                        text,
-                                                        kind: HoverBlockKind::PlainText,
+                            // VS Code shows hover for the meaningful part regardless of where you hover
+                            // Find the first part with actual hover information (tooltip or location)
+                            let _hint_start =
+                                snapshot.anchor_to_inlay_offset(hovered_inlay.position);
+                            let mut part_offset = 0;
+
+                            for part in label_parts {
+                                let part_len = part.value.chars().count();
+
+                                if part.tooltip.is_some() || part.location.is_some() {
+                                    // Found the meaningful part - show hover for it
+                                    let highlight_start = part_offset + extra_shift_left;
+                                    let highlight_end = part_offset + part_len + extra_shift_right;
+
+                                    let highlight = InlayHighlight {
+                                        inlay: hovered_inlay.id,
+                                        inlay_position: hovered_inlay.position,
+                                        range: highlight_start..highlight_end,
+                                    };
+
+                                    if let Some(tooltip) = part.tooltip {
+                                        hover_popover::hover_at_inlay(
+                                            editor,
+                                            InlayHover {
+                                                tooltip: match tooltip {
+                                                    InlayHintLabelPartTooltip::String(text) => {
+                                                        HoverBlock {
+                                                            text,
+                                                            kind: HoverBlockKind::PlainText,
+                                                        }
                                                     }
-                                                }
-                                                InlayHintLabelPartTooltip::MarkupContent(
-                                                    content,
-                                                ) => HoverBlock {
-                                                    text: content.value,
-                                                    kind: content.kind,
+                                                    InlayHintLabelPartTooltip::MarkupContent(
+                                                        content,
+                                                    ) => HoverBlock {
+                                                        text: content.value,
+                                                        kind: content.kind,
+                                                    },
                                                 },
+                                                range: highlight.clone(),
                                             },
-                                            range: highlight.clone(),
-                                        },
-                                        window,
-                                        cx,
-                                    );
-                                    hover_updated = true;
-                                } else if let Some((_language_server_id, location)) =
-                                    &hovered_hint_part.location
-                                {
-                                    // Fallback: Show location info when no tooltip is available
-                                    eprintln!(
-                                        "No tooltip, but has location. Showing type definition info"
-                                    );
-
-                                    // Extract filename from the path
-                                    let filename =
-                                        location.uri.path().split('/').last().unwrap_or("unknown");
-
-                                    // Show information about where this type is defined
-                                    let hover_text = format!(
-                                        "{}\n\nDefined in {} at line {}",
-                                        hovered_hint_part.value.trim(),
-                                        filename,
-                                        location.range.start.line + 1
-                                    );
-
-                                    hover_popover::hover_at_inlay(
-                                        editor,
-                                        InlayHover {
-                                            tooltip: HoverBlock {
-                                                text: hover_text,
-                                                kind: HoverBlockKind::Markdown,
-                                            },
-                                            range: highlight.clone(),
-                                        },
-                                        window,
-                                        cx,
-                                    );
-                                    hover_updated = true;
-                                }
-                                if let Some((language_server_id, location)) =
-                                    hovered_hint_part.location
-                                {
-                                    if secondary_held && !editor.has_pending_nonempty_selection() {
-                                        go_to_definition_updated = true;
-                                        show_link_definition(
-                                            shift_held,
+                                            window,
+                                            cx,
+                                        );
+                                        hover_updated = true;
+                                    } else if let Some((_language_server_id, location)) =
+                                        part.location.clone()
+                                    {
+                                        // When there's no tooltip but we have a location, perform a "Go to Definition" style operation
+                                        // First show a loading message
+                                        let filename = location
+                                            .uri
+                                            .path()
+                                            .split('/')
+                                            .next_back()
+                                            .unwrap_or("unknown")
+                                            .to_string();
+                                        let loading_text = format!(
+                                            "{}\n\nLoading documentation from {}...",
+                                            part.value.trim(),
+                                            filename
+                                        );
+                                        eprintln!(
+                                            "Showing loading message for type: {}",
+                                            part.value.trim()
+                                        );
+                                        hover_popover::hover_at_inlay(
                                             editor,
-                                            TriggerPoint::InlayHint(
-                                                highlight,
-                                                location,
-                                                language_server_id,
-                                            ),
-                                            snapshot,
+                                            InlayHover {
+                                                tooltip: HoverBlock {
+                                                    text: loading_text.clone(),
+                                                    kind: HoverBlockKind::PlainText,
+                                                },
+                                                range: highlight.clone(),
+                                            },
                                             window,
                                             cx,
                                         );
+                                        hover_updated = true;
+
+                                        // Now perform the "Go to Definition" flow to get hover documentation
+                                        if let Some(project) = editor.project.clone() {
+                                            let highlight = highlight.clone();
+                                            let hint_value = part.value.clone();
+                                            let location_uri = location.uri.clone();
+
+                                            cx.spawn_in(window, async move |editor, cx| {
+                                                async move {
+                                                    eprintln!("Starting async documentation fetch for {}", hint_value);
+
+                                                    // 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
+                                                    eprintln!("Converting LSP URI to file path: {}", location_uri);
+                                                    let file_path = location.uri.to_file_path()
+                                                        .map_err(|_| anyhow::anyhow!("Invalid file URL"))?;
+                                                    eprintln!("File path: {:?}", file_path);
+
+                                                    // Open the definition file
+                                                    eprintln!("Opening definition file via project.open_local_buffer");
+                                                    let definition_buffer = project
+                                                        .update(cx, |project, cx| {
+                                                            project.open_local_buffer(file_path, cx)
+                                                        })?
+                                                        .await?;
+                                                    eprintln!("Successfully opened definition buffer");
+
+                                                    // Extract documentation directly from the source
+                                                    let documentation = definition_buffer.update(cx, |buffer, _| {
+                                                        let line_number = location.range.start.line as usize;
+                                                        eprintln!("Looking for documentation at line {}", line_number);
+
+                                                        // 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());
+
+                                                        eprintln!("Found {} doc lines", doc_lines.len());
+
+                                                        if doc_lines.is_empty() {
+                                                            None
+                                                        } else {
+                                                            let docs = doc_lines.join("\n");
+                                                            eprintln!("Extracted docs: {}", docs.chars().take(100).collect::<String>());
+                                                            Some((definition, docs))
+                                                        }
+                                                    })?;
+
+                                                    if let Some((definition, docs)) = documentation {
+                                                        eprintln!("Got documentation from source!");
+
+                                                        // Format as markdown with the definition as a code block
+                                                        let formatted_docs = format!("```rust\n{}\n```\n\n{}", definition, docs);
+
+                                                        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 {
+                                                        eprintln!("No documentation found in source, falling back to location info");
+                                                        // Fallback to showing just the location info
+                                                        let fallback_text = format!(
+                                                            "{}\n\nDefined in {} at line {}",
+                                                            hint_value.trim(),
+                                                            filename,
+                                                            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,
+                                                                },
+                                                                window,
+                                                                cx,
+                                                            );
+                                                        }).log_err();
+                                                    }
+
+                                                    eprintln!("Documentation fetch complete");
+                                                    anyhow::Ok(())
+                                                }
+                                                .log_err()
+                                                .await
+                                            }).detach();
+                                        }
+                                    }
+
+                                    if let Some((language_server_id, location)) = &part.location {
+                                        if secondary_held
+                                            && !editor.has_pending_nonempty_selection()
+                                        {
+                                            go_to_definition_updated = true;
+                                            show_link_definition(
+                                                shift_held,
+                                                editor,
+                                                TriggerPoint::InlayHint(
+                                                    highlight,
+                                                    location.clone(),
+                                                    *language_server_id,
+                                                ),
+                                                snapshot,
+                                                window,
+                                                cx,
+                                            );
+                                        }
                                     }
+
+                                    // Found and processed the meaningful part
+                                    break;
                                 }
-                            } else {
-                                eprintln!("No hovered hint part found");
+
+                                part_offset += part_len;
                             }
                         }
                     };
                 }
-            } else {
-                eprintln!("No cached hint found for id {:?}", hovered_hint.id);
             }
-        } else {
-            eprintln!("No inlay hint found at hovered offset");
         }
-    } else {
-        eprintln!("No hovered offset calculated");
     }
 
     if !go_to_definition_updated {
         editor.hide_hovered_link(cx)
     }
     if !hover_updated {
-        eprintln!("No hover was updated, calling hover_at with None");
         hover_popover::hover_at(editor, None, window, cx);
     }
 }

crates/editor/src/hover_popover.rs 🔗

@@ -132,10 +132,6 @@ pub fn hover_at_inlay(
     window: &mut Window,
     cx: &mut Context<Editor>,
 ) {
-    eprintln!(
-        "hover_at_inlay called - inlay_id: {:?}, range: {:?}",
-        inlay_hover.range.inlay, inlay_hover.range.range
-    );
     if EditorSettings::get_global(cx).hover_popover_enabled {
         if editor.pending_rename.is_some() {
             return;
@@ -163,18 +159,12 @@ pub fn hover_at_inlay(
         }
 
         let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
-        eprintln!(
-            "hover_at_inlay: Creating task with {}ms delay",
-            hover_popover_delay
-        );
 
         let task = cx.spawn_in(window, async move |this, cx| {
             async move {
-                eprintln!("hover_at_inlay task: Starting delay");
                 cx.background_executor()
                     .timer(Duration::from_millis(hover_popover_delay))
                     .await;
-                eprintln!("hover_at_inlay task: Delay complete");
                 this.update(cx, |this, _| {
                     this.hover_state.diagnostic_popover = None;
                 })?;
@@ -207,7 +197,6 @@ pub fn hover_at_inlay(
                 };
 
                 this.update(cx, |this, cx| {
-                    eprintln!("hover_at_inlay task: Setting hover popover and calling notify");
                     // TODO: no background highlights happen for inlays currently
                     this.hover_state.info_popovers = vec![hover_popover];
                     cx.notify();
@@ -220,7 +209,6 @@ pub fn hover_at_inlay(
         });
 
         editor.hover_state.info_task = Some(task);
-        eprintln!("hover_at_inlay: Task stored");
     }
 }