diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2c3e8603f8a979239883276e69ff75e9d498e48d..fb3bdad86555e465b7a45bde5b9e5d5962183c62 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4614,11 +4614,11 @@ impl Element for EditorElement { let gutter_settings = EditorSettings::get_global(cx).gutter; - let mut context_menu_visible = false; + let mut _context_menu_visible = false; let mut code_actions_indicator = None; if let Some(newest_selection_head) = newest_selection_head { if (start_row..end_row).contains(&newest_selection_head.row()) { - context_menu_visible = self.layout_context_menu( + _context_menu_visible = self.layout_context_menu( line_height, &hitbox, &text_hitbox, @@ -4671,7 +4671,7 @@ impl Element for EditorElement { cx, ); - if !context_menu_visible && !cx.has_active_drag() { + if !cx.has_active_drag() { self.layout_hover_popovers( &snapshot, &hitbox, diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 143d5e359ff1a6bc47d6fef6cf1c73610bb7da34..d3b732b986f195a045c9de9d5cdc5cad07808df4 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -612,7 +612,8 @@ impl DiagnosticPopover { mod tests { use super::*; use crate::{ - editor_tests::init_test, + actions::ConfirmCompletion, + editor_tests::{handle_completion_request, init_test}, hover_links::update_inlay_link_and_hover_points, inlay_hint_cache::tests::{cached_hint_labels, visible_hint_labels}, test::editor_lsp_test_context::EditorLspTestContext, @@ -625,10 +626,180 @@ mod tests { use lsp::LanguageServerId; use project::{HoverBlock, HoverBlockKind}; use smol::stream::StreamExt; + use std::sync::atomic; + use std::sync::atomic::AtomicUsize; use text::Bias; use unindent::Unindent; use util::test::marked_text_ranges; + #[gpui::test] + async fn test_mouse_hover_info_popover_with_autocomplete_popover( + cx: &mut gpui::TestAppContext, + ) { + init_test(cx, |_| {}); + const HOVER_DELAY_MILLIS: u64 = 350; + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + let counter = Arc::new(AtomicUsize::new(0)); + // Basic hover delays and then pops without moving the mouse + cx.set_state(indoc! {" + oneˇ + two + three + fn test() { println!(); } + "}); + + //prompt autocompletion menu + cx.simulate_keystroke("."); + handle_completion_request( + &mut cx, + indoc! {" + one.|<> + two + three + "}, + vec!["first_completion", "second_completion"], + counter.clone(), + ) + .await; + cx.condition(|editor, _| editor.context_menu_visible()) // wait until completion menu is visible + .await; + assert_eq!(counter.load(atomic::Ordering::Acquire), 1); // 1 completion request + + let hover_point = cx.display_point(indoc! {" + one. + two + three + fn test() { printˇln!(); } + "}); + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let anchor = snapshot + .buffer_snapshot + .anchor_before(hover_point.to_offset(&snapshot, Bias::Left)); + hover_at(editor, Some(anchor), cx) + }); + assert!(!cx.editor(|editor, _| editor.hover_state.visible())); + + // After delay, hover should be visible. + let symbol_range = cx.lsp_range(indoc! {" + one. + two + three + fn test() { «println!»(); } + "}); + let mut requests = + cx.handle_request::(move |_, _, _| async move { + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "some basic docs".to_string(), + }), + range: Some(symbol_range), + })) + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + requests.next().await; + + cx.editor(|editor, _| { + assert!(editor.hover_state.visible()); + assert_eq!( + editor.hover_state.info_popovers.len(), + 1, + "Expected exactly one hover but got: {:?}", + editor.hover_state.info_popovers + ); + let rendered = editor + .hover_state + .info_popovers + .first() + .cloned() + .unwrap() + .parsed_content; + assert_eq!(rendered.text, "some basic docs".to_string()) + }); + + // check that the completion menu is still visible and that there still has only been 1 completion request + cx.editor(|editor, _| assert!(editor.context_menu_visible())); + assert_eq!(counter.load(atomic::Ordering::Acquire), 1); + + //apply a completion and check it was successfully applied + let _apply_additional_edits = cx.update_editor(|editor, cx| { + editor.context_menu_next(&Default::default(), cx); + editor + .confirm_completion(&ConfirmCompletion::default(), cx) + .unwrap() + }); + cx.assert_editor_state(indoc! {" + one.second_completionˇ + two + three + fn test() { println!(); } + "}); + + // check that the completion menu is no longer visible and that there still has only been 1 completion request + cx.editor(|editor, _| assert!(!editor.context_menu_visible())); + assert_eq!(counter.load(atomic::Ordering::Acquire), 1); + + //verify the information popover is still visible and unchanged + cx.editor(|editor, _| { + assert!(editor.hover_state.visible()); + assert_eq!( + editor.hover_state.info_popovers.len(), + 1, + "Expected exactly one hover but got: {:?}", + editor.hover_state.info_popovers + ); + let rendered = editor + .hover_state + .info_popovers + .first() + .cloned() + .unwrap() + .parsed_content; + assert_eq!(rendered.text, "some basic docs".to_string()) + }); + + // Mouse moved with no hover response dismisses + let hover_point = cx.display_point(indoc! {" + one.second_completionˇ + two + three + fn teˇst() { println!(); } + "}); + let mut request = cx + .lsp + .handle_request::(|_, _| async move { Ok(None) }); + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + let anchor = snapshot + .buffer_snapshot + .anchor_before(hover_point.to_offset(&snapshot, Bias::Left)); + hover_at(editor, Some(anchor), cx) + }); + cx.background_executor + .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); + request.next().await; + + // verify that the information popover is no longer visible + cx.editor(|editor, _| { + assert!(!editor.hover_state.visible()); + }); + } + #[gpui::test] async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {});