From 0aab6d8bdc8ede886cd8f6c746ca8fc16c05c92f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 6 May 2024 09:46:30 -0600 Subject: [PATCH] Fix race condition in editor show_hover (#11441) The DisplayPoint returned from the position map is only valid at the snapshot in the position map. Before this change we were erroneously using it to index into the current version of the buffer. Release Notes: - Fixed a panic caused by a race condition in hover. --- crates/editor/src/element.rs | 2 +- crates/editor/src/hover_popover.rs | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c5fed1fdf4..70f8e2566c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -638,7 +638,7 @@ impl EditorElement { editor.update_hovered_link(point_for_position, &position_map.snapshot, modifiers, cx); if let Some(point) = point_for_position.as_valid() { - hover_at(editor, Some(point), cx); + hover_at(editor, Some((point, &position_map.snapshot)), cx); Self::update_visible_cursor(editor, point, position_map, cx); } else { hover_at(editor, None, cx); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 97ee81ff81..0133b72032 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -31,15 +31,20 @@ pub const HOVER_POPOVER_GAP: Pixels = px(10.); /// Bindable action which uses the most recent selection head to trigger a hover pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext) { let head = editor.selections.newest_display(cx).head(); - show_hover(editor, head, true, cx); + let snapshot = editor.snapshot(cx); + show_hover(editor, head, &snapshot, true, cx); } /// The internal hover action dispatches between `show_hover` or `hide_hover` /// depending on whether a point to hover over is provided. -pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { +pub fn hover_at( + editor: &mut Editor, + point: Option<(DisplayPoint, &EditorSnapshot)>, + cx: &mut ViewContext, +) { if EditorSettings::get_global(cx).hover_popover_enabled { - if let Some(point) = point { - show_hover(editor, point, false, cx); + if let Some((point, snapshot)) = point { + show_hover(editor, point, snapshot, false, cx); } else { hide_hover(editor, cx); } @@ -160,6 +165,7 @@ pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> bool { fn show_hover( editor: &mut Editor, point: DisplayPoint, + snapshot: &EditorSnapshot, ignore_timeout: bool, cx: &mut ViewContext, ) { @@ -167,7 +173,6 @@ fn show_hover( return; } - let snapshot = editor.snapshot(cx); let multibuffer_offset = point.to_offset(&snapshot.display_snapshot, Bias::Left); let (buffer, buffer_position) = if let Some(output) = editor @@ -234,6 +239,7 @@ fn show_hover( return; } } + let snapshot = snapshot.clone(); let task = cx.spawn(|this, mut cx| { async move { @@ -659,7 +665,10 @@ mod tests { fn test() { printˇln!(); } "}); - cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + hover_at(editor, Some((hover_point, &snapshot)), cx) + }); assert!(!cx.editor(|editor, _| editor.hover_state.visible())); // After delay, hover should be visible. @@ -705,7 +714,10 @@ mod tests { let mut request = cx .lsp .handle_request::(|_, _| async move { Ok(None) }); - cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx)); + cx.update_editor(|editor, cx| { + let snapshot = editor.snapshot(cx); + hover_at(editor, Some((hover_point, &snapshot)), cx) + }); cx.background_executor .advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100)); request.next().await;