Reproduce #33715 in a test

Richard Feldman created

Change summary

crates/editor/src/hover_popover.rs | 234 ++++++++++++++++++++++++++++++++
1 file changed, 234 insertions(+)

Detailed changes

crates/editor/src/hover_popover.rs 🔗

@@ -1883,4 +1883,238 @@ mod tests {
             );
         });
     }
+
+    #[gpui::test]
+    async fn test_hover_on_inlay_hint_types(cx: &mut gpui::TestAppContext) {
+        use crate::{DisplayPoint, PointForPosition};
+        use std::sync::Arc;
+        use std::sync::atomic::{self, AtomicUsize};
+
+        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 {
+                        ..Default::default()
+                    }),
+                )),
+                ..Default::default()
+            },
+            cx,
+        )
+        .await;
+
+        cx.set_state(indoc! {r#"
+            fn main() {
+                let foo = "foo".to_string();
+                let bar: String = "bar".to_string();ˇ
+            }
+        "#});
+
+        // Set up inlay hint handler
+        let buffer_text = cx.buffer_text();
+        let hint_position = cx.to_lsp(buffer_text.find("foo =").unwrap() + 3);
+        cx.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
+            move |_, _params, _| async move {
+                Ok(Some(vec![lsp::InlayHint {
+                    position: hint_position,
+                    label: lsp::InlayHintLabel::String(": String".to_string()),
+                    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, visible_hint_labels(editor, cx));
+        });
+
+        // Set up hover handler that should be called for both inlay hint and explicit type
+        let hover_count = Arc::new(AtomicUsize::new(0));
+        let hover_count_clone = hover_count.clone();
+        let _hover_requests =
+            cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, params, _| {
+                let count = hover_count_clone.clone();
+                async move {
+                    let current = count.fetch_add(1, atomic::Ordering::SeqCst);
+                    println!(
+                        "Hover request {} at position: {:?}",
+                        current + 1,
+                        params.text_document_position_params.position
+                    );
+                    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#"
+                fn main() {
+                    let foo« »= "foo".to_string();
+                    let bar: String = "bar".to_string();
+                }
+            "#})
+            .first()
+            .cloned()
+            .unwrap();
+
+        // Create a PointForPosition that simulates hovering over 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);
+            // Position over the "S" in "String" of the inlay hint ": String"
+            let exact_unclipped = DisplayPoint::new(
+                previous_valid.row(),
+                previous_valid.column() + 2, // Skip past ": " to hover over "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 potential hover popover
+        cx.background_executor
+            .advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100));
+        cx.background_executor.run_until_parked();
+
+        // Check if hover request was made for inlay hint
+        let inlay_hover_request_count = hover_count.load(atomic::Ordering::SeqCst);
+        println!(
+            "Hover requests after inlay hint hover: {}",
+            inlay_hover_request_count
+        );
+
+        // Check if hover popover is shown for inlay hint
+        let has_inlay_hover = cx.editor(|editor, _, _| {
+            println!(
+                "Inlay hint hover - info_popovers: {}",
+                editor.hover_state.info_popovers.len()
+            );
+            println!(
+                "Inlay hint hover - info_task: {:?}",
+                editor.hover_state.info_task.is_some()
+            );
+            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
+        // Find the position of "String" in the explicit type annotation
+        let explicit_string_point = cx.display_point(indoc! {r#"
+            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
+        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, _, _| {
+            println!(
+                "Explicit type hover - info_popovers: {}",
+                editor.hover_state.info_popovers.len()
+            );
+            println!(
+                "Explicit type hover - info_task: {:?}",
+                editor.hover_state.info_task.is_some()
+            );
+            editor.hover_state.info_popovers.len() > 0
+        });
+
+        // Check total hover requests
+        let total_requests = hover_count.load(atomic::Ordering::SeqCst);
+        println!("Total hover requests: {}", total_requests);
+
+        // The test should fail here - inlay hints don't show hover popovers but explicit types do
+        println!("Has inlay hover: {}", has_inlay_hover);
+        println!("Has explicit hover: {}", has_explicit_hover);
+
+        // This test demonstrates issue #33715: hovering over type information in inlay hints
+        // does not show hover popovers, while hovering over explicit types does.
+
+        // Expected behavior: Both should show hover popovers
+        // Actual behavior: Only explicit types show hover popovers
+
+        assert!(
+            has_explicit_hover,
+            "Hover popover should be shown when hovering over explicit type"
+        );
+
+        // This assertion should fail, demonstrating the bug
+        assert!(
+            has_inlay_hover,
+            "Hover popover should be shown when hovering over inlay hint type (Issue #33715). \
+             Inlay hint hover requests: {}, Explicit type hover requests: {}",
+            inlay_hover_request_count,
+            total_requests - inlay_hover_request_count
+        );
+    }
 }