Fix inlay hint hover by not clearing hover when mouse is over inlay

Richard Feldman created

When hovering over an inlay hint, point_for_position.as_valid() returns None
because inlays don't have valid text positions. This was causing hover_at(editor, None)
to be called, which would hide any active hovers.

The fix is simple: don't call hover_at when we're over an inlay position.
The inlay hover is already handled by update_hovered_link, so we don't need
to do anything else.

Change summary

crates/editor/src/editor.rs           |  97 -----------
crates/editor/src/hover_links.rs      | 251 +++++++++++----------------
crates/editor/src/hover_popover.rs    | 253 ----------------------------
crates/editor/src/inlay_hint_cache.rs |  85 +++------
crates/project/src/lsp_store.rs       | 161 ------------------
5 files changed, 141 insertions(+), 706 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1061,11 +1061,10 @@ pub struct Editor {
     read_only: bool,
     leader_id: Option<CollaboratorId>,
     remote_id: Option<ViewId>,
-    hover_state: HoverState,
+    pub hover_state: HoverState,
     pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
     gutter_hovered: bool,
     hovered_link_state: Option<HoveredLinkState>,
-    resolved_inlay_hints_pending_hover: HashSet<InlayId>,
     edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
     code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
     active_inline_completion: Option<InlineCompletionState>,
@@ -2080,7 +2079,6 @@ impl Editor {
             hover_state: HoverState::default(),
             pending_mouse_down: None,
             hovered_link_state: None,
-            resolved_inlay_hints_pending_hover: HashSet::default(),
             edit_prediction_provider: None,
             active_inline_completion: None,
             stale_inline_completion_in_menu: None,
@@ -20354,99 +20352,6 @@ impl Editor {
         &self.inlay_hint_cache
     }
 
-    pub fn check_resolved_inlay_hint_hover(
-        &mut self,
-        inlay_id: InlayId,
-        excerpt_id: ExcerptId,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> bool {
-        if !self.resolved_inlay_hints_pending_hover.remove(&inlay_id) {
-            return false;
-        }
-        // Get the resolved hint from the cache
-        if let Some(cached_hint) = self.inlay_hint_cache.hint_by_id(excerpt_id, inlay_id) {
-            // Check if we have tooltip data to display
-            let mut hover_to_show = None;
-
-            // Check main tooltip
-            if let Some(tooltip) = &cached_hint.tooltip {
-                let inlay_hint = self
-                    .visible_inlay_hints(cx)
-                    .into_iter()
-                    .find(|hint| hint.id == inlay_id);
-
-                if let Some(inlay_hint) = inlay_hint {
-                    let range = crate::InlayHighlight {
-                        inlay: inlay_id,
-                        inlay_position: inlay_hint.position,
-                        range: 0..inlay_hint.text.len(),
-                    };
-                    hover_to_show = Some((tooltip.clone(), range));
-                }
-            } else if let project::InlayHintLabel::LabelParts(parts) = &cached_hint.label {
-                // Check label parts for tooltips
-                let inlay_hint = self
-                    .visible_inlay_hints(cx)
-                    .into_iter()
-                    .find(|hint| hint.id == inlay_id);
-
-                if let Some(inlay_hint) = inlay_hint {
-                    let mut offset = 0;
-                    for part in parts {
-                        if let Some(part_tooltip) = &part.tooltip {
-                            let range = crate::InlayHighlight {
-                                inlay: inlay_id,
-                                inlay_position: inlay_hint.position,
-                                range: offset..offset + part.value.len(),
-                            };
-                            // Convert InlayHintLabelPartTooltip to InlayHintTooltip
-                            let tooltip = match part_tooltip {
-                                project::InlayHintLabelPartTooltip::String(text) => {
-                                    project::InlayHintTooltip::String(text.clone())
-                                }
-                                project::InlayHintLabelPartTooltip::MarkupContent(content) => {
-                                    project::InlayHintTooltip::MarkupContent(content.clone())
-                                }
-                            };
-                            hover_to_show = Some((tooltip, range));
-                            break;
-                        }
-                        offset += part.value.len();
-                    }
-                }
-            }
-
-            // Show the hover if we have tooltip data
-            if let Some((tooltip, range)) = hover_to_show {
-                use crate::hover_popover::{InlayHover, hover_at_inlay};
-                use project::{HoverBlock, HoverBlockKind, InlayHintTooltip};
-
-                let hover_block = match tooltip {
-                    InlayHintTooltip::String(text) => HoverBlock {
-                        text,
-                        kind: HoverBlockKind::PlainText,
-                    },
-                    InlayHintTooltip::MarkupContent(content) => HoverBlock {
-                        text: content.value,
-                        kind: content.kind,
-                    },
-                };
-
-                hover_at_inlay(
-                    self,
-                    InlayHover {
-                        tooltip: hover_block,
-                        range,
-                    },
-                    window,
-                    cx,
-                );
-            }
-        }
-        true
-    }
-
     pub fn replay_insert_event(
         &mut self,
         text: &str,

crates/editor/src/hover_links.rs 🔗

@@ -121,22 +121,6 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         let hovered_link_modifier = Editor::multi_cursor_modifier(false, &modifiers, cx);
-
-        // Allow inlay hover points to be updated even without modifier key
-        if point_for_position.as_valid().is_none() {
-            // Hovering over inlay - check for hover tooltips
-            update_inlay_link_and_hover_points(
-                snapshot,
-                point_for_position,
-                self,
-                hovered_link_modifier,
-                modifiers.shift,
-                window,
-                cx,
-            );
-            return;
-        }
-
         if !hovered_link_modifier || self.has_pending_selection() {
             self.hide_hovered_link(cx);
             return;
@@ -153,7 +137,15 @@ impl Editor {
                 show_link_definition(modifiers.shift, self, trigger_point, snapshot, window, cx);
             }
             None => {
-                // This case is now handled above
+                update_inlay_link_and_hover_points(
+                    snapshot,
+                    point_for_position,
+                    self,
+                    hovered_link_modifier,
+                    modifiers.shift,
+                    window,
+                    cx,
+                );
             }
         }
     }
@@ -327,164 +319,129 @@ pub fn update_inlay_link_and_hover_points(
             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) {
-                // Check if we should process this hint for hover
-                let should_process_hint = match cached_hint.resolve_state {
+                match cached_hint.resolve_state {
                     ResolveState::CanResolve(_, _) => {
-                        // Check if the hint already has the data we need (tooltip in label parts)
-                        if let project::InlayHintLabel::LabelParts(label_parts) = &cached_hint.label
-                        {
-                            let has_tooltip_parts =
-                                label_parts.iter().any(|part| part.tooltip.is_some());
-                            if has_tooltip_parts {
-                                true // Process the hint
-                            } else {
-                                if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-                                    inlay_hint_cache.spawn_hint_resolve(
-                                        buffer_id,
-                                        excerpt_id,
-                                        hovered_hint.id,
-                                        window,
-                                        cx,
-                                    );
-                                }
-                                false // Don't process further
-                            }
-                        } else {
-                            if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-                                inlay_hint_cache.spawn_hint_resolve(
-                                    buffer_id,
-                                    excerpt_id,
-                                    hovered_hint.id,
-                                    window,
-                                    cx,
-                                );
-                            }
-                            false // Don't process further
+                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+                            inlay_hint_cache.spawn_hint_resolve(
+                                buffer_id,
+                                excerpt_id,
+                                hovered_hint.id,
+                                window,
+                                cx,
+                            );
                         }
                     }
                     ResolveState::Resolved => {
-                        true // Process the hint
-                    }
-                    ResolveState::Resolving => {
-                        // Check if this hint was just resolved and needs hover
-                        if editor.check_resolved_inlay_hint_hover(
-                            hovered_hint.id,
-                            excerpt_id,
-                            window,
-                            cx,
-                        ) {
-                            return; // Hover was shown by check_resolved_inlay_hint_hover
+                        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;
                         }
-                        false // Don't process yet
-                    }
-                };
-
-                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_hint.id,
-                                            inlay_position: hovered_hint.position,
-                                            range: extra_shift_left
-                                                ..hovered_hint.text.len() + extra_shift_right,
-                                        },
-                                    },
-                                    window,
-                                    cx,
-                                );
-                                hover_updated = true;
-                            }
+                        if cached_hint.padding_right {
+                            extra_shift_right += 1;
                         }
-                        project::InlayHintLabel::LabelParts(label_parts) => {
-                            let hint_start = snapshot.anchor_to_inlay_offset(hovered_hint.position);
-                            if let Some((hovered_hint_part, part_range)) =
-                                hover_popover::find_hovered_hint_part(
-                                    label_parts,
-                                    hint_start,
-                                    hovered_offset,
-                                )
-                            {
-                                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 {
+                        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;
                                 }
-                                if let Some((language_server_id, location)) =
-                                    hovered_hint_part.location
+                            }
+                            project::InlayHintLabel::LabelParts(label_parts) => {
+                                let hint_start =
+                                    snapshot.anchor_to_inlay_offset(hovered_hint.position);
+                                if let Some((hovered_hint_part, part_range)) =
+                                    hover_popover::find_hovered_hint_part(
+                                        label_parts,
+                                        hint_start,
+                                        hovered_offset,
+                                    )
                                 {
-                                    if secondary_held && !editor.has_pending_nonempty_selection() {
-                                        go_to_definition_updated = true;
-                                        show_link_definition(
-                                            shift_held,
+                                    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 {
+                                        hover_popover::hover_at_inlay(
                                             editor,
-                                            TriggerPoint::InlayHint(
-                                                highlight,
-                                                location,
-                                                language_server_id,
-                                            ),
-                                            snapshot,
+                                            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;
+                                    }
+                                    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,
+                                                editor,
+                                                TriggerPoint::InlayHint(
+                                                    highlight,
+                                                    location,
+                                                    language_server_id,
+                                                ),
+                                                snapshot,
+                                                window,
+                                                cx,
+                                            );
+                                        }
                                     }
                                 }
                             }
-                        }
-                    };
+                        };
+                    }
+                    ResolveState::Resolving => {}
                 }
             }
         }

crates/editor/src/hover_popover.rs 🔗

@@ -55,15 +55,7 @@ pub fn hover_at(
         if let Some(anchor) = anchor {
             show_hover(editor, anchor, false, window, cx);
         } else {
-            // Don't hide hover if there's an active inlay hover
-            let has_inlay_hover = editor
-                .hover_state
-                .info_popovers
-                .iter()
-                .any(|popover| matches!(popover.symbol_range, RangeInEditor::Inlay(_)));
-            if !has_inlay_hover {
-                hide_hover(editor, cx);
-            }
+            hide_hover(editor, cx);
         }
     }
 }
@@ -159,12 +151,7 @@ pub fn hover_at_inlay(
                 false
             })
         {
-            return;
-        }
-
-        // Check if we have an in-progress hover task for a different location
-        if editor.hover_state.info_task.is_some() {
-            editor.hover_state.info_task = None;
+            hide_hover(editor, cx);
         }
 
         let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
@@ -214,9 +201,7 @@ pub fn hover_at_inlay(
                 anyhow::Ok(())
             }
             .log_err()
-            .await;
-
-            Some(())
+            .await
         });
 
         editor.hover_state.info_task = Some(task);
@@ -1898,236 +1883,4 @@ mod tests {
             );
         });
     }
-
-    #[gpui::test]
-    async fn test_hover_on_inlay_hint_types(cx: &mut gpui::TestAppContext) {
-        use crate::{DisplayPoint, PointForPosition};
-
-        init_test(cx, |settings| {
-            settings.defaults.inlay_hints = Some(InlayHintSettings {
-                enabled: true,
-                show_type_hints: true,
-                show_value_hints: true,
-                show_parameter_hints: true,
-                show_other_hints: true,
-                edit_debounce_ms: 0,
-                scroll_debounce_ms: 0,
-                show_background: false,
-                toggle_on_modifiers_press: None,
-            });
-        });
-
-        let mut cx = EditorLspTestContext::new_rust(
-            lsp::ServerCapabilities {
-                hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
-                inlay_hint_provider: Some(lsp::OneOf::Right(
-                    lsp::InlayHintServerCapabilities::Options(lsp::InlayHintOptions {
-                        resolve_provider: Some(true),
-                        ..Default::default()
-                    }),
-                )),
-                ..Default::default()
-            },
-            cx,
-        )
-        .await;
-
-        cx.set_state(indoc! {r#"
-            struct String;
-
-            fn main() {
-                let foo = "foo".to_string();
-                let bar: String = "bar".to_string();ˇ
-            }
-        "#});
-
-        // Set up inlay hint handler with proper label parts that include locations
-        let buffer_text = cx.buffer_text();
-        let hint_position = cx.to_lsp(buffer_text.find("foo =").unwrap() + 3);
-        let string_type_range = cx.lsp_range(indoc! {r#"
-            struct «String»;
-
-            fn main() {
-                let foo = "foo".to_string();
-                let bar: String = "bar".to_string();
-            }
-        "#});
-        let uri = cx.buffer_lsp_url.clone();
-
-        cx.set_request_handler::<lsp::request::InlayHintRequest, _, _>(move |_, _params, _| {
-            let uri = uri.clone();
-            async move {
-                Ok(Some(vec![lsp::InlayHint {
-                    position: hint_position,
-                    label: lsp::InlayHintLabel::LabelParts(vec![
-                        lsp::InlayHintLabelPart {
-                            value: ": ".to_string(),
-                            location: None,
-                            tooltip: None,
-                            command: None,
-                        },
-                        lsp::InlayHintLabelPart {
-                            value: "String".to_string(),
-                            location: Some(lsp::Location {
-                                uri: uri.clone(),
-                                range: string_type_range,
-                            }),
-                            tooltip: Some(lsp::InlayHintLabelPartTooltip::MarkupContent(
-                                lsp::MarkupContent {
-                                    kind: lsp::MarkupKind::Markdown,
-                                    value: "```rust\nstruct String\n```\n\nA UTF-8 encoded, growable string.".to_string(),
-                                }
-                            )),
-                            command: None,
-                        },
-                    ]),
-                    kind: Some(lsp::InlayHintKind::TYPE),
-                    text_edits: None,
-                    tooltip: None,
-                    padding_left: Some(false),
-                    padding_right: Some(false),
-                    data: None,
-                }]))
-            }
-        })
-        .next()
-        .await;
-
-        cx.background_executor.run_until_parked();
-
-        // Verify inlay hint is displayed
-        cx.update_editor(|editor, _, cx| {
-            let expected_layers = vec![": String".to_string()];
-            assert_eq!(expected_layers, cached_hint_labels(editor));
-            assert_eq!(expected_layers, visible_hint_labels(editor, cx));
-        });
-
-        // Set up hover handler for explicit type hover
-        let mut hover_requests =
-            cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _params, _| {
-                async move {
-                    // Return hover info for any hover request (both line 0 and line 4 have String types)
-                    Ok(Some(lsp::Hover {
-                        contents: lsp::HoverContents::Markup(lsp::MarkupContent {
-                            kind: lsp::MarkupKind::Markdown,
-                            value:
-                                "```rust\nstruct String\n```\n\nA UTF-8 encoded, growable string."
-                                    .to_string(),
-                        }),
-                        range: None,
-                    }))
-                }
-            });
-
-        // Test hovering over the inlay hint type
-        // Get the position where the inlay hint is displayed
-        let inlay_range = cx
-            .ranges(indoc! {r#"
-                struct String;
-
-                fn main() {
-                    let foo« »= "foo".to_string();
-                    let bar: String = "bar".to_string();
-                }
-            "#})
-            .first()
-            .cloned()
-            .unwrap();
-
-        // Create a PointForPosition that simulates hovering over the "String" part of the inlay hint
-        let point_for_position = cx.update_editor(|editor, window, cx| {
-            let snapshot = editor.snapshot(window, cx);
-            let previous_valid = inlay_range.start.to_display_point(&snapshot);
-            let next_valid = inlay_range.end.to_display_point(&snapshot);
-            // The hint text is ": String", we want to hover over "String" which starts at index 2
-            // Add a bit more to ensure we're well within "String" (e.g., over the 'r')
-            let exact_unclipped = DisplayPoint::new(
-                previous_valid.row(),
-                previous_valid.column() + 4, // Position over 'r' in "String"
-            );
-            PointForPosition {
-                previous_valid,
-                next_valid,
-                exact_unclipped,
-                column_overshoot_after_line_end: 0,
-            }
-        });
-
-        // Update hovered link to trigger hover logic for inlay hints
-        cx.update_editor(|editor, window, cx| {
-            let snapshot = editor.snapshot(window, cx);
-            update_inlay_link_and_hover_points(
-                &snapshot,
-                point_for_position,
-                editor,
-                false, // secondary_held
-                false, // shift_held
-                window,
-                cx,
-            );
-        });
-
-        // Wait for the popover to appear
-        cx.background_executor
-            .advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
-        cx.background_executor.run_until_parked();
-
-        // Check if hover popover is shown for inlay hint
-        let has_inlay_hover = cx.editor(|editor, _, _| editor.hover_state.info_popovers.len() > 0);
-
-        // Clear hover state
-        cx.update_editor(|editor, _, cx| {
-            hide_hover(editor, cx);
-        });
-
-        // Test hovering over the explicit type
-        let explicit_string_point = cx.display_point(indoc! {r#"
-            struct String;
-
-            fn main() {
-                let foo = "foo".to_string();
-                let bar: Sˇtring = "bar".to_string();
-            }
-        "#});
-
-        // Use hover_at to trigger hover on explicit type
-        cx.update_editor(|editor, window, cx| {
-            let snapshot = editor.snapshot(window, cx);
-            let anchor = snapshot
-                .buffer_snapshot
-                .anchor_before(explicit_string_point.to_offset(&snapshot, Bias::Left));
-            hover_at(editor, Some(anchor), window, cx);
-        });
-
-        // Wait for hover request and give time for the popover to appear
-        hover_requests.next().await;
-        cx.background_executor
-            .advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
-        cx.background_executor.run_until_parked();
-
-        // Check if hover popover is shown for explicit type
-        let has_explicit_hover =
-            cx.editor(|editor, _, _| editor.hover_state.info_popovers.len() > 0);
-
-        // Both should show hover popovers
-        assert!(
-            has_explicit_hover,
-            "Hover popover should be shown when hovering over explicit type"
-        );
-
-        assert!(
-            has_inlay_hover,
-            "Hover popover should be shown when hovering over inlay hint type"
-        );
-
-        // NOTE: This test demonstrates the proper fix for issue #33715:
-        // Language servers should provide inlay hints with label parts that include
-        // tooltip information. When users hover over a type in an inlay hint,
-        // they see the tooltip content, which should contain the same information
-        // as hovering over an actual type annotation.
-        //
-        // The issue occurs when language servers provide inlay hints as plain strings
-        // (e.g., ": String") without any tooltip information. In that case, there's
-        // no way for Zed to know what documentation to show when hovering.
-    }
 }

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -21,7 +21,6 @@ use clock::Global;
 use futures::future;
 use gpui::{AppContext as _, AsyncApp, Context, Entity, Task, Window};
 use language::{Buffer, BufferSnapshot, language_settings::InlayHintKind};
-use lsp::LanguageServerId;
 use parking_lot::RwLock;
 use project::{InlayHint, ResolveState};
 
@@ -623,67 +622,45 @@ impl InlayHintCache {
             let mut guard = excerpt_hints.write();
             if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
                 if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state {
-                    let server_id = *server_id;
                     let hint_to_resolve = cached_hint.clone();
+                    let server_id = *server_id;
                     cached_hint.resolve_state = ResolveState::Resolving;
                     drop(guard);
-                    self.resolve_hint(
-                        server_id,
-                        buffer_id,
-                        excerpt_id,
-                        id,
-                        hint_to_resolve,
-                        window,
-                        cx,
-                    )
+                    cx.spawn_in(window, async move |editor, cx| {
+                        let resolved_hint_task = editor.update(cx, |editor, cx| {
+                            let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
+                            editor.semantics_provider.as_ref()?.resolve_inlay_hint(
+                                hint_to_resolve,
+                                buffer,
+                                server_id,
+                                cx,
+                            )
+                        })?;
+                        if let Some(resolved_hint_task) = resolved_hint_task {
+                            let mut resolved_hint =
+                                resolved_hint_task.await.context("hint resolve task")?;
+                            editor.read_with(cx, |editor, _| {
+                                if let Some(excerpt_hints) =
+                                    editor.inlay_hint_cache.hints.get(&excerpt_id)
+                                {
+                                    let mut guard = excerpt_hints.write();
+                                    if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
+                                        if cached_hint.resolve_state == ResolveState::Resolving {
+                                            resolved_hint.resolve_state = ResolveState::Resolved;
+                                            *cached_hint = resolved_hint;
+                                        }
+                                    }
+                                }
+                            })?;
+                        }
+
+                        anyhow::Ok(())
+                    })
                     .detach_and_log_err(cx);
                 }
             }
         }
     }
-
-    fn resolve_hint(
-        &self,
-        server_id: LanguageServerId,
-        buffer_id: BufferId,
-        excerpt_id: ExcerptId,
-        inlay_id: InlayId,
-        hint_to_resolve: InlayHint,
-        window: &mut Window,
-        cx: &mut Context<Editor>,
-    ) -> Task<anyhow::Result<()>> {
-        cx.spawn_in(window, async move |editor, cx| {
-            let resolved_hint_task = editor.update(cx, |editor, cx| {
-                let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
-                editor.semantics_provider.as_ref()?.resolve_inlay_hint(
-                    hint_to_resolve,
-                    buffer,
-                    server_id,
-                    cx,
-                )
-            })?;
-            if let Some(resolved_hint_task) = resolved_hint_task {
-                let mut resolved_hint = resolved_hint_task.await.context("hint resolve task")?;
-                editor.update(cx, |editor, cx| {
-                    if let Some(excerpt_hints) = editor.inlay_hint_cache.hints.get(&excerpt_id) {
-                        let mut guard = excerpt_hints.write();
-                        if let Some(cached_hint) = guard.hints_by_id.get_mut(&inlay_id) {
-                            if cached_hint.resolve_state == ResolveState::Resolving {
-                                resolved_hint.resolve_state = ResolveState::Resolved;
-                                *cached_hint = resolved_hint;
-                            }
-                        }
-                    }
-
-                    // Mark the hint as resolved and needing hover check
-                    editor.resolved_inlay_hints_pending_hover.insert(inlay_id);
-                    cx.notify();
-                })?;
-            }
-
-            anyhow::Ok(())
-        })
-    }
 }
 
 fn debounce_value(debounce_ms: u64) -> Option<Duration> {

crates/project/src/lsp_store.rs 🔗

@@ -4965,10 +4965,7 @@ impl LspStore {
                 return Task::ready(Ok(hint));
             }
             let buffer_snapshot = buffer_handle.read(cx).snapshot();
-            // Preserve the original hint data before resolution
-            let original_hint = hint.clone();
-
-            cx.spawn(async move |_this, cx| {
+            cx.spawn(async move |_, cx| {
                 let resolve_task = lang_server.request::<lsp::request::InlayHintResolveRequest>(
                     InlayHints::project_to_lsp_hint(hint, &buffer_snapshot),
                 );
@@ -4976,139 +4973,7 @@ impl LspStore {
                     .await
                     .into_response()
                     .context("inlay hint resolve LSP request")?;
-
-                // Check if we need to fetch hover info as a fallback
-                let needs_fallback = match &resolved_hint.label {
-                    lsp::InlayHintLabel::String(_) => resolved_hint.tooltip.is_none(),
-                    lsp::InlayHintLabel::LabelParts(parts) => {
-                        resolved_hint.tooltip.is_none()
-                            && parts
-                                .iter()
-                                .any(|p| p.tooltip.is_none() && p.location.is_some())
-                    }
-                };
-
-                let mut resolved_hint = resolved_hint;
-
-                if let lsp::InlayHintLabel::LabelParts(parts) = &resolved_hint.label {
-                    for (i, part) in parts.iter().enumerate() {
-                            "  Part {}: value='{}', tooltip={:?}, location={:?}",
-                            i, part.value, part.tooltip, part.location
-                        );
-                    }
-                }
-
-                if needs_fallback {
-                    // For label parts with locations but no tooltips, fetch hover info
-                    if let lsp::InlayHintLabel::LabelParts(parts) = &mut resolved_hint.label {
-                        for part in parts.iter_mut() {
-                            if part.tooltip.is_none() {
-                                if let Some(location) = &part.location {
-
-                                    // Open the document
-                                    let did_open_params = lsp::DidOpenTextDocumentParams {
-                                        text_document: lsp::TextDocumentItem {
-                                            uri: location.uri.clone(),
-                                            language_id: "rust".to_string(), // TODO: Detect language
-                                            version: 0,
-                                            text: std::fs::read_to_string(location.uri.path())
-                                                .unwrap_or_else(|_| String::new()),
-                                        },
-                                    };
-
-                                    lang_server.notify::<lsp::notification::DidOpenTextDocument>(
-                                        &did_open_params,
-                                    )?;
-
-                                    // Request hover at the location
-                                    let hover_params = lsp::HoverParams {
-                                        text_document_position_params:
-                                            lsp::TextDocumentPositionParams {
-                                                text_document: lsp::TextDocumentIdentifier {
-                                                    uri: location.uri.clone(),
-                                                },
-                                                position: location.range.start,
-                                            },
-                                        work_done_progress_params: Default::default(),
-                                    };
-
-                                    if let Ok(hover_response) = lang_server
-                                        .request::<lsp::request::HoverRequest>(hover_params)
-                                        .await
-                                        .into_response()
-                                    {
-
-                                        if let Some(hover) = hover_response {
-                                            // Convert hover contents to tooltip
-                                            part.tooltip = Some(match hover.contents {
-                                                lsp::HoverContents::Scalar(content) => {
-                                                    lsp::InlayHintLabelPartTooltip::String(
-                                                        match content {
-                                                            lsp::MarkedString::String(s) => s,
-                                                            lsp::MarkedString::LanguageString(
-                                                                ls,
-                                                            ) => ls.value,
-                                                        },
-                                                    )
-                                                }
-                                                lsp::HoverContents::Array(contents) => {
-                                                    let combined = contents
-                                                        .into_iter()
-                                                        .map(|c| match c {
-                                                            lsp::MarkedString::String(s) => s,
-                                                            lsp::MarkedString::LanguageString(
-                                                                ls,
-                                                            ) => ls.value,
-                                                        })
-                                                        .collect::<Vec<_>>()
-                                                        .join("\n\n");
-                                                    lsp::InlayHintLabelPartTooltip::String(combined)
-                                                }
-                                                lsp::HoverContents::Markup(markup) => {
-                                                    lsp::InlayHintLabelPartTooltip::MarkupContent(
-                                                        markup,
-                                                    )
-                                                }
-                                            });
-                                        }
-                                    }
-
-                                    // Close the document
-                                    let did_close_params = lsp::DidCloseTextDocumentParams {
-                                        text_document: lsp::TextDocumentIdentifier {
-                                            uri: location.uri.clone(),
-                                        },
-                                    };
-
-                                    lang_server.notify::<lsp::notification::DidCloseTextDocument>(
-                                        &did_close_params,
-                                    )?;
-                                }
-                            }
-                        }
-                    }
-                }
-
-                // Check if we need to restore location data from the original hint
-                let mut resolved_hint = resolved_hint;
-                if let (
-                    lsp::InlayHintLabel::LabelParts(resolved_parts),
-                    crate::InlayHintLabel::LabelParts(original_parts),
-                ) = (&mut resolved_hint.label, &original_hint.label)
-                {
-                    for (resolved_part, original_part) in
-                        resolved_parts.iter_mut().zip(original_parts.iter())
-                    {
-                        if resolved_part.location.is_none() && original_part.location.is_some() {
-                            // Restore location from original hint
-                            if let Some((_server_id, location)) = &original_part.location {
-                                resolved_part.location = Some(location.clone());
-                            }
-                        }
-                    }
-                }
-
-                let mut resolved_hint = InlayHints::lsp_to_project_hint(
+                let resolved_hint = InlayHints::lsp_to_project_hint(
                     resolved_hint,
                     &buffer_handle,
                     server_id,
@@ -5117,28 +4982,6 @@ impl LspStore {
                     cx,
                 )
                 .await?;
-
-                // Final check: if resolved hint still has no tooltip but original had location,
-                // preserve the original hint's data
-                if resolved_hint.tooltip.is_none() {
-                    if let (
-                        crate::InlayHintLabel::LabelParts(resolved_parts),
-                        crate::InlayHintLabel::LabelParts(original_parts),
-                    ) = (&mut resolved_hint.label, &original_hint.label)
-                    {
-                        for (resolved_part, original_part) in
-                            resolved_parts.iter_mut().zip(original_parts.iter())
-                        {
-                            if resolved_part.tooltip.is_none()
-                                && resolved_part.location.is_none()
-                                && original_part.location.is_some()
-                            {
-                                resolved_part.location = original_part.location.clone();
-                            }
-                        }
-                    }
-                }
-
                 Ok(resolved_hint)
             })
         }