Extract resolve_hint

Richard Feldman and Cole Miller created

Co-authored-by: Cole Miller <cole@zed.dev>

Change summary

crates/editor/src/hover_links.rs      | 244 ++++++++++++++++------------
crates/editor/src/inlay_hint_cache.rs |  75 +++++---
2 files changed, 182 insertions(+), 137 deletions(-)

Detailed changes

crates/editor/src/hover_links.rs 🔗

@@ -121,6 +121,22 @@ impl Editor {
         cx: &mut Context<Self>,
     ) {
         let hovered_link_modifier = Editor::multi_cursor_modifier(false, &modifiers, cx);
+
+        // Allow inlay hover points to be updated even without modifier key
+        if point_for_position.as_valid().is_none() {
+            // Hovering over inlay - check for hover tooltips
+            update_inlay_link_and_hover_points(
+                snapshot,
+                point_for_position,
+                self,
+                hovered_link_modifier,
+                modifiers.shift,
+                window,
+                cx,
+            );
+            return;
+        }
+
         if !hovered_link_modifier || self.has_pending_selection() {
             self.hide_hovered_link(cx);
             return;
@@ -137,15 +153,7 @@ impl Editor {
                 show_link_definition(modifiers.shift, self, trigger_point, snapshot, window, cx);
             }
             None => {
-                update_inlay_link_and_hover_points(
-                    snapshot,
-                    point_for_position,
-                    self,
-                    hovered_link_modifier,
-                    modifiers.shift,
-                    window,
-                    cx,
-                );
+                // This case is now handled above
             }
         }
     }
@@ -319,129 +327,155 @@ pub fn update_inlay_link_and_hover_points(
             let inlay_hint_cache = editor.inlay_hint_cache();
             let excerpt_id = previous_valid_anchor.excerpt_id;
             if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
-                match cached_hint.resolve_state {
+                // Check if we should process this hint for hover
+                let should_process_hint = match cached_hint.resolve_state {
                     ResolveState::CanResolve(_, _) => {
-                        if let Some(buffer_id) = previous_valid_anchor.buffer_id {
-                            inlay_hint_cache.spawn_hint_resolve(
-                                buffer_id,
-                                excerpt_id,
-                                hovered_hint.id,
-                                window,
-                                cx,
-                            );
+                        // Check if the hint already has the data we need (tooltip in label parts)
+                        if let project::InlayHintLabel::LabelParts(label_parts) = &cached_hint.label
+                        {
+                            let has_tooltip_parts =
+                                label_parts.iter().any(|part| part.tooltip.is_some());
+                            if has_tooltip_parts {
+                                true // Process the hint
+                            } else {
+                                if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+                                    inlay_hint_cache.spawn_hint_resolve(
+                                        buffer_id,
+                                        excerpt_id,
+                                        hovered_hint.id,
+                                        window,
+                                        cx,
+                                    );
+                                }
+                                false // Don't process further
+                            }
+                        } else {
+                            if let Some(buffer_id) = previous_valid_anchor.buffer_id {
+                                inlay_hint_cache.spawn_hint_resolve(
+                                    buffer_id,
+                                    excerpt_id,
+                                    hovered_hint.id,
+                                    window,
+                                    cx,
+                                );
+                            }
+                            false // Don't process further
                         }
                     }
                     ResolveState::Resolved => {
-                        let mut extra_shift_left = 0;
-                        let mut extra_shift_right = 0;
-                        if cached_hint.padding_left {
-                            extra_shift_left += 1;
-                            extra_shift_right += 1;
-                        }
-                        if cached_hint.padding_right {
-                            extra_shift_right += 1;
+                        true // Process the hint
+                    }
+                    ResolveState::Resolving => {
+                        false // Don't process yet
+                    }
+                };
+
+                if should_process_hint {
+                    let mut extra_shift_left = 0;
+                    let mut extra_shift_right = 0;
+                    if cached_hint.padding_left {
+                        extra_shift_left += 1;
+                        extra_shift_right += 1;
+                    }
+                    if cached_hint.padding_right {
+                        extra_shift_right += 1;
+                    }
+                    match cached_hint.label {
+                        project::InlayHintLabel::String(_) => {
+                            if let Some(tooltip) = cached_hint.tooltip {
+                                hover_popover::hover_at_inlay(
+                                    editor,
+                                    InlayHover {
+                                        tooltip: match tooltip {
+                                            InlayHintTooltip::String(text) => HoverBlock {
+                                                text,
+                                                kind: HoverBlockKind::PlainText,
+                                            },
+                                            InlayHintTooltip::MarkupContent(content) => {
+                                                HoverBlock {
+                                                    text: content.value,
+                                                    kind: content.kind,
+                                                }
+                                            }
+                                        },
+                                        range: InlayHighlight {
+                                            inlay: hovered_hint.id,
+                                            inlay_position: hovered_hint.position,
+                                            range: extra_shift_left
+                                                ..hovered_hint.text.len() + extra_shift_right,
+                                        },
+                                    },
+                                    window,
+                                    cx,
+                                );
+                                hover_updated = true;
+                            }
                         }
-                        match cached_hint.label {
-                            project::InlayHintLabel::String(_) => {
-                                if let Some(tooltip) = cached_hint.tooltip {
+                        project::InlayHintLabel::LabelParts(label_parts) => {
+                            let hint_start = snapshot.anchor_to_inlay_offset(hovered_hint.position);
+                            if let Some((hovered_hint_part, part_range)) =
+                                hover_popover::find_hovered_hint_part(
+                                    label_parts,
+                                    hint_start,
+                                    hovered_offset,
+                                )
+                            {
+                                let highlight_start =
+                                    (part_range.start - hint_start).0 + extra_shift_left;
+                                let highlight_end =
+                                    (part_range.end - hint_start).0 + extra_shift_right;
+                                let highlight = InlayHighlight {
+                                    inlay: hovered_hint.id,
+                                    inlay_position: hovered_hint.position,
+                                    range: highlight_start..highlight_end,
+                                };
+                                if let Some(tooltip) = hovered_hint_part.tooltip {
                                     hover_popover::hover_at_inlay(
                                         editor,
                                         InlayHover {
                                             tooltip: match tooltip {
-                                                InlayHintTooltip::String(text) => HoverBlock {
-                                                    text,
-                                                    kind: HoverBlockKind::PlainText,
-                                                },
-                                                InlayHintTooltip::MarkupContent(content) => {
+                                                InlayHintLabelPartTooltip::String(text) => {
                                                     HoverBlock {
-                                                        text: content.value,
-                                                        kind: content.kind,
+                                                        text,
+                                                        kind: HoverBlockKind::PlainText,
                                                     }
                                                 }
+                                                InlayHintLabelPartTooltip::MarkupContent(
+                                                    content,
+                                                ) => HoverBlock {
+                                                    text: content.value,
+                                                    kind: content.kind,
+                                                },
                                             },
-                                            range: InlayHighlight {
-                                                inlay: hovered_hint.id,
-                                                inlay_position: hovered_hint.position,
-                                                range: extra_shift_left
-                                                    ..hovered_hint.text.len() + extra_shift_right,
-                                            },
+                                            range: highlight.clone(),
                                         },
                                         window,
                                         cx,
                                     );
                                     hover_updated = true;
                                 }
-                            }
-                            project::InlayHintLabel::LabelParts(label_parts) => {
-                                let hint_start =
-                                    snapshot.anchor_to_inlay_offset(hovered_hint.position);
-                                if let Some((hovered_hint_part, part_range)) =
-                                    hover_popover::find_hovered_hint_part(
-                                        label_parts,
-                                        hint_start,
-                                        hovered_offset,
-                                    )
+                                if let Some((language_server_id, location)) =
+                                    hovered_hint_part.location
                                 {
-                                    let highlight_start =
-                                        (part_range.start - hint_start).0 + extra_shift_left;
-                                    let highlight_end =
-                                        (part_range.end - hint_start).0 + extra_shift_right;
-                                    let highlight = InlayHighlight {
-                                        inlay: hovered_hint.id,
-                                        inlay_position: hovered_hint.position,
-                                        range: highlight_start..highlight_end,
-                                    };
-                                    if let Some(tooltip) = hovered_hint_part.tooltip {
-                                        hover_popover::hover_at_inlay(
+                                    if secondary_held && !editor.has_pending_nonempty_selection() {
+                                        go_to_definition_updated = true;
+                                        show_link_definition(
+                                            shift_held,
                                             editor,
-                                            InlayHover {
-                                                tooltip: match tooltip {
-                                                    InlayHintLabelPartTooltip::String(text) => {
-                                                        HoverBlock {
-                                                            text,
-                                                            kind: HoverBlockKind::PlainText,
-                                                        }
-                                                    }
-                                                    InlayHintLabelPartTooltip::MarkupContent(
-                                                        content,
-                                                    ) => HoverBlock {
-                                                        text: content.value,
-                                                        kind: content.kind,
-                                                    },
-                                                },
-                                                range: highlight.clone(),
-                                            },
+                                            TriggerPoint::InlayHint(
+                                                highlight,
+                                                location,
+                                                language_server_id,
+                                            ),
+                                            snapshot,
                                             window,
                                             cx,
                                         );
-                                        hover_updated = true;
-                                    }
-                                    if let Some((language_server_id, location)) =
-                                        hovered_hint_part.location
-                                    {
-                                        if secondary_held
-                                            && !editor.has_pending_nonempty_selection()
-                                        {
-                                            go_to_definition_updated = true;
-                                            show_link_definition(
-                                                shift_held,
-                                                editor,
-                                                TriggerPoint::InlayHint(
-                                                    highlight,
-                                                    location,
-                                                    language_server_id,
-                                                ),
-                                                snapshot,
-                                                window,
-                                                cx,
-                                            );
-                                        }
                                     }
                                 }
                             }
-                        };
-                    }
-                    ResolveState::Resolving => {}
+                        }
+                    };
                 }
             }
         }

crates/editor/src/inlay_hint_cache.rs 🔗

@@ -21,6 +21,7 @@ use clock::Global;
 use futures::future;
 use gpui::{AppContext as _, AsyncApp, Context, Entity, Task, Window};
 use language::{Buffer, BufferSnapshot, language_settings::InlayHintKind};
+use lsp::LanguageServerId;
 use parking_lot::RwLock;
 use project::{InlayHint, ResolveState};
 
@@ -622,45 +623,55 @@ impl InlayHintCache {
             let mut guard = excerpt_hints.write();
             if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
                 if let ResolveState::CanResolve(server_id, _) = &cached_hint.resolve_state {
-                    let hint_to_resolve = cached_hint.clone();
                     let server_id = *server_id;
+                    let mut cached_hint = cached_hint.clone();
                     cached_hint.resolve_state = ResolveState::Resolving;
                     drop(guard);
-                    cx.spawn_in(window, async move |editor, cx| {
-                        let resolved_hint_task = editor.update(cx, |editor, cx| {
-                            let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
-                            editor.semantics_provider.as_ref()?.resolve_inlay_hint(
-                                hint_to_resolve,
-                                buffer,
-                                server_id,
-                                cx,
-                            )
-                        })?;
-                        if let Some(resolved_hint_task) = resolved_hint_task {
-                            let mut resolved_hint =
-                                resolved_hint_task.await.context("hint resolve task")?;
-                            editor.read_with(cx, |editor, _| {
-                                if let Some(excerpt_hints) =
-                                    editor.inlay_hint_cache.hints.get(&excerpt_id)
-                                {
-                                    let mut guard = excerpt_hints.write();
-                                    if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
-                                        if cached_hint.resolve_state == ResolveState::Resolving {
-                                            resolved_hint.resolve_state = ResolveState::Resolved;
-                                            *cached_hint = resolved_hint;
-                                        }
-                                    }
-                                }
-                            })?;
-                        }
-
-                        anyhow::Ok(())
-                    })
-                    .detach_and_log_err(cx);
+                    self.resolve_hint(server_id, buffer_id, cached_hint, window, cx)
+                        .detach_and_log_err(cx);
                 }
             }
         }
     }
+
+    fn resolve_hint(
+        &self,
+        server_id: LanguageServerId,
+        buffer_id: BufferId,
+        hint_to_resolve: InlayHint,
+        window: &mut Window,
+        cx: &mut Context<Editor>,
+    ) -> Task<anyhow::Result<()>> {
+        cx.spawn_in(window, async move |editor, cx| {
+            let resolved_hint_task = editor.update(cx, |editor, cx| {
+                let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
+                editor.semantics_provider.as_ref()?.resolve_inlay_hint(
+                    hint_to_resolve,
+                    buffer,
+                    server_id,
+                    cx,
+                )
+            })?;
+            if let Some(resolved_hint_task) = resolved_hint_task {
+                let mut resolved_hint = resolved_hint_task.await.context("hint resolve task")?;
+                editor.update(cx, |editor, cx| {
+                    if let Some(excerpt_hints) = editor.inlay_hint_cache.hints.get(&excerpt_id) {
+                        let mut guard = excerpt_hints.write();
+                        if let Some(cached_hint) = guard.hints_by_id.get_mut(&id) {
+                            if cached_hint.resolve_state == ResolveState::Resolving {
+                                resolved_hint.resolve_state = ResolveState::Resolved;
+                                *cached_hint = resolved_hint;
+                            }
+                        }
+                    }
+                    // Notify to trigger UI update
+                    cx.notify();
+                })?;
+            }
+
+            anyhow::Ok(())
+        })
+    }
 }
 
 fn debounce_value(debounce_ms: u64) -> Option<Duration> {