wip

Richard Feldman created

Change summary

crates/editor/src/hover_links.rs | 801 ++++++++++++++++++++++-----------
crates/project/src/project.rs    |   6 
2 files changed, 547 insertions(+), 260 deletions(-)

Detailed changes

crates/editor/src/hover_links.rs 🔗

@@ -287,320 +287,601 @@ pub fn update_inlay_link_and_hover_points(
     window: &mut Window,
     cx: &mut Context<Editor>,
 ) {
+    let mut go_to_definition_updated = false;
+    let mut hover_updated = false;
+
     // For inlay hints, we need to use the exact position where the mouse is
     // 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;
-
     // 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 {
+    if point_for_position.column_overshoot_after_line_end == 0 {
+        let hovered_offset =
+            snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left);
+        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,
+        );
+        if let Some(hovered_hint) = editor
+            .visible_inlay_hints(cx)
+            .into_iter()
+            .skip_while(|hint| {
+                hint.position
+                    .cmp(&previous_valid_anchor, &buffer_snapshot)
+                    .is_lt()
+            })
+            .take_while(|hint| {
+                hint.position
+                    .cmp(&next_valid_anchor, &buffer_snapshot)
+                    .is_le()
+            })
+            .max_by_key(|hint| 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) {
+                match cached_hint.resolve_state {
                     ResolveState::CanResolve(_, _) => {
-                        // For unresolved hints, spawn resolution
-                        if let Some(buffer_id) = hovered_inlay.position.buffer_id {
+                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
                             inlay_hint_cache.spawn_hint_resolve(
                                 buffer_id,
                                 excerpt_id,
-                                hovered_inlay.id,
+                                hovered_hint.id,
                                 window,
                                 cx,
                             );
                         }
-                        false // Don't process unresolved hints
-                    }
-                    ResolveState::Resolved => true,
-                    ResolveState::Resolving => false,
-                };
 
-                if should_process_hint {
-                    let mut extra_shift_left = 0;
-                    let mut extra_shift_right = 0;
-                    if cached_hint.padding_left {
-                        extra_shift_left += 1;
-                        extra_shift_right += 1;
-                    }
-                    if cached_hint.padding_right {
-                        extra_shift_right += 1;
-                    }
-                    match cached_hint.label {
-                        project::InlayHintLabel::String(_) => {
-                            if let Some(tooltip) = cached_hint.tooltip {
-                                hover_popover::hover_at_inlay(
-                                    editor,
-                                    InlayHover {
-                                        tooltip: match tooltip {
-                                            InlayHintTooltip::String(text) => HoverBlock {
-                                                text,
-                                                kind: HoverBlockKind::PlainText,
-                                            },
-                                            InlayHintTooltip::MarkupContent(content) => {
-                                                HoverBlock {
-                                                    text: content.value,
-                                                    kind: content.kind,
-                                                }
-                                            }
-                                        },
-                                        range: InlayHighlight {
-                                            inlay: hovered_inlay.id,
-                                            inlay_position: hovered_inlay.position,
-                                            range: extra_shift_left
-                                                ..hovered_inlay.text.len() + extra_shift_right,
-                                        },
-                                    },
-                                    window,
-                                    cx,
-                                );
-                                hover_updated = true;
+                        if cached_hint.resolve_state.is_resolved() {
+                            let mut extra_shift_left = 0;
+                            let mut extra_shift_right = 0;
+                            if cached_hint.padding_left {
+                                extra_shift_left += 1;
+                                extra_shift_right += 1;
                             }
-                        }
-                        project::InlayHintLabel::LabelParts(label_parts) => {
-                            // 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 {
+                            if cached_hint.padding_right {
+                                extra_shift_right += 1;
+                            }
+                            match cached_hint.label {
+                                project::InlayHintLabel::String(_) => {
+                                    if let Some(tooltip) = cached_hint.tooltip {
                                         hover_popover::hover_at_inlay(
                                             editor,
                                             InlayHover {
                                                 tooltip: match tooltip {
-                                                    InlayHintLabelPartTooltip::String(text) => {
+                                                    InlayHintTooltip::String(text) => HoverBlock {
+                                                        text,
+                                                        kind: HoverBlockKind::PlainText,
+                                                    },
+                                                    InlayHintTooltip::MarkupContent(content) => {
                                                         HoverBlock {
-                                                            text,
-                                                            kind: HoverBlockKind::PlainText,
+                                                            text: content.value,
+                                                            kind: content.kind,
                                                         }
                                                     }
-                                                    InlayHintLabelPartTooltip::MarkupContent(
-                                                        content,
-                                                    ) => HoverBlock {
-                                                        text: content.value,
-                                                        kind: content.kind,
-                                                    },
                                                 },
-                                                range: highlight.clone(),
+                                                range: InlayHighlight {
+                                                    inlay: hovered_hint.id,
+                                                    inlay_position: hovered_hint.position,
+                                                    range: extra_shift_left
+                                                        ..hovered_hint.text.len()
+                                                            + extra_shift_right,
+                                                },
                                             },
                                             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
-                                        let filename = location
-                                            .uri
-                                            .path()
-                                            .split('/')
-                                            .next_back()
-                                            .unwrap_or("unknown")
-                                            .to_string();
+                                    }
+                                }
+                                project::InlayHintLabel::LabelParts(label_parts) => {
+                                    // Find the first part with actual hover information (tooltip or location)
+                                    let _hint_start =
+                                        snapshot.anchor_to_inlay_offset(hovered_hint.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_hint.id,
+                                                inlay_position: hovered_hint.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,
+                                                            },
+                                                        },
+                                                        range: highlight.clone(),
+                                                    },
+                                                    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
+                                                let filename = location
+                                                    .uri
+                                                    .path()
+                                                    .split('/')
+                                                    .next_back()
+                                                    .unwrap_or("unknown")
+                                                    .to_string();
+
+                                                hover_popover::hover_at_inlay(
+                                                    editor,
+                                                    InlayHover {
+                                                        tooltip: HoverBlock {
+                                                            text: "Loading documentation..."
+                                                                .to_string(),
+                                                            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 {
+                                                            // 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?;
+
+                                                            // 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);
+                                                                }
 
-                                        hover_popover::hover_at_inlay(
-                                            editor,
-                                            InlayHover {
-                                                tooltip: HoverBlock {
-                                                    text: "Loading documentation...".to_string(),
+                                                                // 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))
+                                                                }
+                                                            })?;
+
+                                                            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);
+
+                                                                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,
+                                                                    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();
+                                                            }
+
+                                                            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,
+                                                    );
+                                                }
+                                            }
+
+                                            break;
+                                        }
+
+                                        part_offset += part_len;
+                                    }
+                                }
+                            };
+                        }
+                    }
+                    ResolveState::Resolved => {
+                        let mut extra_shift_left = 0;
+                        let mut extra_shift_right = 0;
+                        if cached_hint.padding_left {
+                            extra_shift_left += 1;
+                            extra_shift_right += 1;
+                        }
+                        if cached_hint.padding_right {
+                            extra_shift_right += 1;
+                        }
+                        match cached_hint.label {
+                            project::InlayHintLabel::String(_) => {
+                                if let Some(tooltip) = cached_hint.tooltip {
+                                    hover_popover::hover_at_inlay(
+                                        editor,
+                                        InlayHover {
+                                            tooltip: match tooltip {
+                                                InlayHintTooltip::String(text) => HoverBlock {
+                                                    text,
                                                     kind: HoverBlockKind::PlainText,
                                                 },
-                                                range: highlight.clone(),
+                                                InlayHintTooltip::MarkupContent(content) => {
+                                                    HoverBlock {
+                                                        text: content.value,
+                                                        kind: content.kind,
+                                                    }
+                                                }
                                             },
-                                            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 {
-                                                    // 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?;
-
-                                                    // 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);
+                                            range: InlayHighlight {
+                                                inlay: hovered_hint.id,
+                                                inlay_position: hovered_hint.position,
+                                                range: extra_shift_left
+                                                    ..hovered_hint.text.len() + extra_shift_right,
+                                            },
+                                        },
+                                        window,
+                                        cx,
+                                    );
+                                    hover_updated = true;
+                                }
+                            }
+                            project::InlayHintLabel::LabelParts(label_parts) => {
+                                // 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_hint.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_hint.id,
+                                            inlay_position: hovered_hint.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,
+                                                            }
                                                         }
-
-                                                        // Collect doc comments
-                                                        while current_line > 0 {
-                                                            if let Some(line) = lines.get(current_line) {
+                                                        InlayHintLabelPartTooltip::MarkupContent(
+                                                            content,
+                                                        ) => HoverBlock {
+                                                            text: content.value,
+                                                            kind: content.kind,
+                                                        },
+                                                    },
+                                                    range: highlight.clone(),
+                                                },
+                                                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
+                                            let filename = location
+                                                .uri
+                                                .path()
+                                                .split('/')
+                                                .next_back()
+                                                .unwrap_or("unknown")
+                                                .to_string();
+
+                                            hover_popover::hover_at_inlay(
+                                                editor,
+                                                InlayHover {
+                                                    tooltip: HoverBlock {
+                                                        text: "Loading documentation..."
+                                                            .to_string(),
+                                                        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 {
+                                                        // 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?;
+
+                                                        // 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();
-                                                                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;
+                                                                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);
                                                             }
-                                                            current_line = current_line.saturating_sub(1);
-                                                        }
 
-                                                        // Reverse to get correct order
-                                                        doc_lines.reverse();
+                                                            // 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());
+                                                            // 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))
-                                                        }
-                                                    })?;
-
-                                                    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);
-
-                                                        editor.update_in(cx, |editor, window, cx| {
-                                                            hover_popover::hover_at_inlay(
-                                                                editor,
-                                                                InlayHover {
-                                                                    tooltip: HoverBlock {
-                                                                        text: formatted_docs,
-                                                                        kind: HoverBlockKind::Markdown,
+                                                            if doc_lines.is_empty() {
+                                                                None
+                                                            } else {
+                                                                let docs = doc_lines.join("\n");
+                                                                Some((definition, docs))
+                                                            }
+                                                        })?;
+
+                                                        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);
+
+                                                            editor.update_in(cx, |editor, window, cx| {
+                                                                hover_popover::hover_at_inlay(
+                                                                    editor,
+                                                                    InlayHover {
+                                                                        tooltip: HoverBlock {
+                                                                            text: formatted_docs,
+                                                                            kind: HoverBlockKind::Markdown,
+                                                                        },
+                                                                        range: highlight,
                                                                     },
-                                                                    range: highlight,
-                                                                },
-                                                                window,
-                                                                cx,
+                                                                    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,
+                                                                location.range.start.line + 1
                                                             );
-                                                        }).log_err();
-                                                    } else {
-                                                        // 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,
+                                                            editor.update_in(cx, |editor, window, cx| {
+                                                                hover_popover::hover_at_inlay(
+                                                                    editor,
+                                                                    InlayHover {
+                                                                        tooltip: HoverBlock {
+                                                                            text: fallback_text,
+                                                                            kind: HoverBlockKind::PlainText,
+                                                                        },
+                                                                        range: highlight,
                                                                     },
-                                                                    range: highlight,
-                                                                },
-                                                                window,
-                                                                cx,
-                                                            );
-                                                        }).log_err();
-                                                    }
+                                                                    window,
+                                                                    cx,
+                                                                );
+                                                            }).log_err();
+                                                        }
 
-                                                    anyhow::Ok(())
-                                                }
-                                                .log_err()
-                                                .await
-                                            }).detach();
+                                                        anyhow::Ok(())
+                                                    }
+                                                    .log_err()
+                                                    .await
+                                                }).detach();
+                                            }
                                         }
-                                    }
 
-                                    if let Some((language_server_id, location)) = &part.location {
-                                        if secondary_held
-                                            && !editor.has_pending_nonempty_selection()
+                                        if let Some((language_server_id, location)) = &part.location
                                         {
-                                            go_to_definition_updated = true;
-                                            show_link_definition(
-                                                shift_held,
-                                                editor,
-                                                TriggerPoint::InlayHint(
-                                                    highlight,
-                                                    location.clone(),
-                                                    *language_server_id,
-                                                ),
-                                                snapshot,
-                                                window,
-                                                cx,
-                                            );
+                                            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,
+                                                );
+                                            }
                                         }
+
+                                        break;
                                     }
 
-                                    break;
+                                    part_offset += part_len;
                                 }
-
-                                part_offset += part_len;
                             }
-                        }
-                    };
+                        };
+                    }
                 }
             }
         }

crates/project/src/project.rs 🔗

@@ -656,6 +656,12 @@ pub enum ResolveState {
     Resolving,
 }
 
+impl ResolveState {
+    pub fn is_resolved(&self) -> bool {
+        self == &Self::Resolved
+    }
+}
+
 impl InlayHint {
     pub fn text(&self) -> String {
         match &self.label {