From c6772f83cabbc108114d34f6ae1b3b11a0f17972 Mon Sep 17 00:00:00 2001 From: Tim Vermeulen Date: Mon, 20 Apr 2026 09:57:28 +0200 Subject: [PATCH] editor: Immediately cancel popover task if the hover ends before the popover is shown (#53168) Hover popovers aren't immediately dismissed when the hover ends, but this delay mechanism is currently causing popovers to sometimes still briefly appear when the hover lasted too short to have triggered it. Fixed by immediately cancelling the popover task when the hover ends before the popover is displayed. Most easily reproduced when setting the hover popover delay to a value lower than the default 300ms (but it still happens sometimes with the default value), because the popover hide delay is also 300ms. Here's it with `"hover_popover_delay": 200`: https://github.com/user-attachments/assets/6415d112-d8e0-4a87-9a79-a7ab559f20f2 After the fix: https://github.com/user-attachments/assets/34782389-de4c-4a25-bd6e-4858b55028de Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - Fixed hover popovers sometimes briefly appearing after the hover already ended. --------- Co-authored-by: Lukas Wirth --- crates/editor/src/hover_popover.rs | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 730adec4f9b6b13ea14fc00c447b37bf77156b94..21177ad27b5886739ce5f57421412226ae4b1123 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -62,6 +62,8 @@ pub fn hover_at( editor.hover_state.hiding_delay_task = None; editor.hover_state.closest_mouse_distance = None; show_hover(editor, anchor, false, window, cx); + } else if !editor.hover_state.visible() { + editor.hover_state.info_task = None; } else { let settings = EditorSettings::get_global(cx); if !settings.hover_popover_sticky { @@ -1501,6 +1503,64 @@ mod tests { }); } + #[gpui::test] + async fn test_mouse_hover_cancelled_before_delay(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + hover_provider: Some(lsp::HoverProviderCapability::Simple(true)), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {" + fn ˇtest() { println!(); } + "}); + let hover_point = cx.display_point(indoc! {" + fn test() { printˇln!(); } + "}); + + cx.update_editor(|editor, window, cx| { + let snapshot = editor.snapshot(window, cx); + let anchor = snapshot + .buffer_snapshot() + .anchor_before(hover_point.to_offset(&snapshot, Bias::Left)); + hover_at(editor, Some(anchor), None, window, cx); + hover_at(editor, None, None, window, cx); + }); + + let request_count = Arc::new(AtomicUsize::new(0)); + cx.set_request_handler::({ + let request_count = request_count.clone(); + move |_, _, _| { + let request_count = request_count.clone(); + async move { + request_count.fetch_add(1, atomic::Ordering::Release); + Ok(Some(lsp::Hover { + contents: lsp::HoverContents::Markup(lsp::MarkupContent { + kind: lsp::MarkupKind::Markdown, + value: "some basic docs".to_string(), + }), + range: None, + })) + } + } + }); + + cx.background_executor + .advance_clock(Duration::from_millis(get_hover_popover_delay(&cx) + 100)); + cx.background_executor.run_until_parked(); + cx.run_until_parked(); + + assert_eq!(request_count.load(atomic::Ordering::Acquire), 0); + cx.editor(|editor, _, _| { + assert!(!editor.hover_state.visible()); + }); + } + #[gpui::test] async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {});