From 0d6232b373530001d3e8ecf4526627d547c5335a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Thu, 10 Jul 2025 19:14:36 -0400 Subject: [PATCH] Add a hover when hovering over inlays --- crates/editor/src/element.rs | 4 +- crates/editor/src/hover_links.rs | 108 +++++++++++++++++++++-------- crates/editor/src/hover_popover.rs | 14 +++- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d4cd126f32..1f324e3b2f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1238,8 +1238,8 @@ impl EditorElement { hover_at(editor, Some(anchor), window, cx); Self::update_visible_cursor(editor, point, position_map, window, cx); } else { - // Don't call hover_at with None when we're over an inlay - // The inlay hover is already handled by update_hovered_link + // When over an inlay or invalid position, clear any existing hover + hover_at(editor, None, window, cx); } } else { editor.hide_hovered_link(cx); diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 96c38e9ad5..6bfca303c2 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -3,11 +3,11 @@ use crate::{ GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase, display_map::InlayOffset, editor_settings::GoToDefinitionFallback, - hover_popover::{self, InlayHover}, + hover_popover::{self, HoverState, InlayHover}, scroll::ScrollAmount, }; use gpui::{App, AsyncWindowContext, Context, Entity, Modifiers, Task, Window, px}; -use language::{Bias, ToOffset, point_from_lsp}; +use language::{Bias, ToOffset}; use linkify::{LinkFinder, LinkKind}; use lsp::LanguageServerId; use project::{ @@ -18,6 +18,7 @@ use settings::Settings; use std::ops::Range; use text; use theme::ActiveTheme as _; + use util::{ResultExt, TryFutureExt as _, maybe}; #[derive(Debug)] @@ -164,6 +165,7 @@ impl Editor { pub(crate) fn hide_hovered_link(&mut self, cx: &mut Context) { self.hovered_link_state.take(); self.clear_highlights::(cx); + self.clear_background_highlights::(cx); } pub(crate) fn handle_click_hovered_link( @@ -337,15 +339,11 @@ pub fn update_inlay_link_and_hover_points( window, cx, ); - // Don't set hover_updated during resolution to prevent empty tooltip - // hover_updated = true; } false // Don't process unresolved hints } ResolveState::Resolved => true, ResolveState::Resolving => { - // Don't set hover_updated during resolution to prevent empty tooltip - // hover_updated = true; false // Don't process further } }; @@ -466,36 +464,96 @@ pub fn update_inlay_link_and_hover_points( if let Some(project) = editor.project.clone() { let highlight = highlight.clone(); let hint_value = part.value.clone(); - let location_uri = location.uri.clone(); cx.spawn_in(window, async move |editor, cx| { async move { - eprintln!("Starting async documentation fetch for {}", hint_value); - - // Small delay to show the loading message first - cx.background_executor() - .timer(std::time::Duration::from_millis(50)) - .await; - // Convert LSP URL to file path - eprintln!("Converting LSP URI to file path: {}", location_uri); let file_path = location.uri.to_file_path() .map_err(|_| anyhow::anyhow!("Invalid file URL"))?; - eprintln!("File path: {:?}", file_path); // Open the definition file - eprintln!("Opening definition file via project.open_local_buffer"); let definition_buffer = project .update(cx, |project, cx| { project.open_local_buffer(file_path, cx) })? .await?; - eprintln!("Successfully opened definition buffer"); - // Extract documentation directly from the source + // Register the buffer with language servers + let _lsp_handle = project.update(cx, |project, cx| { + project.register_buffer_with_language_servers(&definition_buffer, cx) + })?; + + // Give LSP a moment to process the didOpen notification + cx.background_executor() + .timer(std::time::Duration::from_millis(100)) + .await; + + // Try to get hover documentation from LSP + let hover_position = location.range.start; + + // Convert LSP position to a point + let hover_point = definition_buffer.update(cx, |buffer, _| { + let point_utf16 = language::point_from_lsp(hover_position); + let snapshot = buffer.snapshot(); + let point = snapshot.clip_point_utf16(point_utf16, Bias::Left); + snapshot.anchor_after(point) + })?; + + let hover_response = project + .update(cx, |project, cx| { + project.hover(&definition_buffer, hover_point, cx) + })? + .await; + + if !hover_response.is_empty() { + // Get the first hover response + let hover = &hover_response[0]; + if !hover.contents.is_empty() { + // Format the hover blocks as markdown + let mut formatted_docs = String::new(); + + // Add the type signature first + formatted_docs.push_str(&format!("```rust\n{}\n```\n\n", hint_value.trim())); + + // Add all the hover content + for block in &hover.contents { + match &block.kind { + HoverBlockKind::Markdown => { + formatted_docs.push_str(&block.text); + formatted_docs.push_str("\n\n"); + } + HoverBlockKind::Code { language } => { + formatted_docs.push_str(&format!("```{}\n{}\n```\n\n", language, block.text)); + } + HoverBlockKind::PlainText => { + formatted_docs.push_str(&block.text); + formatted_docs.push_str("\n\n"); + } + } + } + + editor.update_in(cx, |editor, window, cx| { + hover_popover::hover_at_inlay( + editor, + InlayHover { + tooltip: HoverBlock { + text: formatted_docs.trim().to_string(), + kind: HoverBlockKind::Markdown, + }, + range: highlight, + }, + window, + cx, + ); + }).log_err(); + + return Ok(()); + } + } + + // Fallback: Extract documentation directly from the source let documentation = definition_buffer.update(cx, |buffer, _| { let line_number = location.range.start.line as usize; - eprintln!("Looking for documentation at line {}", line_number); // Get the text of the buffer let text = buffer.text(); @@ -538,25 +596,20 @@ pub fn update_inlay_link_and_hover_points( .map(|s| s.trim().to_string()) .unwrap_or_else(|| hint_value.clone()); - eprintln!("Found {} doc lines", doc_lines.len()); - if doc_lines.is_empty() { None } else { let docs = doc_lines.join("\n"); - eprintln!("Extracted docs: {}", docs.chars().take(100).collect::()); Some((definition, docs)) } })?; if let Some((definition, docs)) = documentation { - eprintln!("Got documentation from source!"); - // Format as markdown with the definition as a code block let formatted_docs = format!("```rust\n{}\n```\n\n{}", definition, docs); editor.update_in(cx, |editor, window, cx| { - hover_popover::hover_at_inlay( + hover_popover::hover_at_inlay( editor, InlayHover { tooltip: HoverBlock { @@ -570,7 +623,6 @@ pub fn update_inlay_link_and_hover_points( ); }).log_err(); } else { - eprintln!("No documentation found in source, falling back to location info"); // Fallback to showing just the location info let fallback_text = format!( "{}\n\nDefined in {} at line {}", @@ -594,7 +646,6 @@ pub fn update_inlay_link_and_hover_points( }).log_err(); } - eprintln!("Documentation fetch complete"); anyhow::Ok(()) } .log_err() @@ -641,6 +692,7 @@ pub fn update_inlay_link_and_hover_points( } if !hover_updated { hover_popover::hover_at(editor, None, window, cx); + editor.clear_background_highlights::(cx); } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 39ae7c7bde..80eb56645e 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -1,6 +1,6 @@ use crate::{ ActiveDiagnostic, Anchor, AnchorRangeExt, DisplayPoint, DisplayRow, Editor, EditorSettings, - EditorSnapshot, GlobalDiagnosticRenderer, Hover, + EditorSnapshot, GlobalDiagnosticRenderer, HighlightStyle, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, scroll::ScrollAmount, @@ -193,7 +193,17 @@ pub fn hover_at_inlay( }; this.update(cx, |this, cx| { - // TODO: no background highlights happen for inlays currently + // Highlight the inlay using background highlighting + let highlight_range = inlay_hover.range.clone(); + this.highlight_inlays::( + vec![highlight_range], + HighlightStyle { + background_color: Some(cx.theme().colors().element_hover), + ..Default::default() + }, + cx, + ); + this.hover_state.info_popovers = vec![hover_popover]; cx.notify(); })?;