@@ -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,
@@ -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::<lsp::request::HoverRequest, _, _>(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::<lsp::request::HoverRequest, _, _>(|_, _| 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, |_| {});