Kinda make this work

This commit is contained in:
Richard Feldman 2025-07-10 17:14:30 -04:00
parent a322aa33c7
commit 8ee82395b8
No known key found for this signature in database
2 changed files with 269 additions and 200 deletions

View file

@ -1,6 +1,7 @@
use crate::{
Anchor, Editor, EditorSettings, EditorSnapshot, FindAllReferences, GoToDefinition,
GoToTypeDefinition, GotoDefinitionKind, InlayId, Navigated, PointForPosition, SelectPhase,
display_map::InlayOffset,
editor_settings::GoToDefinitionFallback,
hover_popover::{self, InlayHover},
scroll::ScrollAmount,
@ -15,6 +16,7 @@ use project::{
};
use settings::Settings;
use std::ops::Range;
use text;
use theme::ActiveTheme as _;
use util::{ResultExt, TryFutureExt as _, maybe};
@ -293,93 +295,45 @@ pub fn update_inlay_link_and_hover_points(
window: &mut Window,
cx: &mut Context<Editor>,
) {
eprintln!("update_inlay_link_and_hover_points called");
// For inlay hints, we need to use the exact position where the mouse is
let hovered_offset = if point_for_position.column_overshoot_after_line_end == 0 {
// Use the exact unclipped position to get the correct offset within the inlay
Some(snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left))
} else {
None
};
// But we must clip it to valid bounds to avoid panics
let clipped_point = snapshot.clip_point(point_for_position.exact_unclipped, Bias::Left);
let hovered_offset = snapshot.display_point_to_inlay_offset(clipped_point, Bias::Left);
let mut go_to_definition_updated = false;
let mut hover_updated = false;
if let Some(hovered_offset) = hovered_offset {
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor = buffer_snapshot.anchor_at(
point_for_position.previous_valid.to_point(snapshot),
Bias::Left,
);
let next_valid_anchor = buffer_snapshot.anchor_at(
point_for_position.next_valid.to_point(snapshot),
Bias::Right,
);
eprintln!(
"Looking for inlay hints between {:?} and {:?}",
previous_valid_anchor, next_valid_anchor
);
let visible_hints = editor.visible_inlay_hints(cx);
eprintln!("Total visible inlay hints: {}", visible_hints.len());
if let Some(hovered_hint) = visible_hints
.into_iter()
.skip_while(|hint| {
let cmp = hint
.position
.cmp(&previous_valid_anchor, &buffer_snapshot)
.is_lt();
eprintln!(
"Checking hint {:?} at {:?} < prev {:?}: {}",
hint.id, hint.position, previous_valid_anchor, cmp
);
cmp
})
.take_while(|hint| {
let cmp = hint
.position
.cmp(&next_valid_anchor, &buffer_snapshot)
.is_le();
eprintln!(
"Checking hint {:?} at {:?} <= next {:?}: {}",
hint.id, hint.position, next_valid_anchor, cmp
);
cmp
})
.max_by_key(|hint| hint.id)
{
eprintln!("Found hovered inlay hint: {:?}", hovered_hint.id);
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) {
eprintln!(
"Cached hint state: {:?}, has tooltip: {}, label: {:?}",
cached_hint.resolve_state,
cached_hint.tooltip.is_some(),
match &cached_hint.label {
project::InlayHintLabel::String(s) => format!("String({})", s),
project::InlayHintLabel::LabelParts(parts) =>
format!("LabelParts({} parts)", parts.len()),
}
);
// Debug full hint content
eprintln!("Full cached hint: {:?}", cached_hint);
if let project::InlayHintLabel::LabelParts(ref parts) = cached_hint.label {
for (i, part) in parts.iter().enumerate() {
eprintln!(
" Part {}: value='{}', tooltip={:?}, location={:?}",
i, part.value, part.tooltip, part.location
);
}
}
// Get all visible inlay hints
let visible_hints = editor.visible_inlay_hints(cx);
// Find if we're hovering over an inlay hint
if let Some(hovered_inlay) = visible_hints.into_iter().find(|inlay| {
// Only process hint inlays
if !matches!(inlay.id, InlayId::Hint(_)) {
return false;
}
// Check if the hovered position falls within this inlay's display range
let inlay_start = snapshot.anchor_to_inlay_offset(inlay.position);
let inlay_end = InlayOffset(inlay_start.0 + inlay.text.len());
hovered_offset >= inlay_start && hovered_offset < inlay_end
}) {
let inlay_hint_cache = editor.inlay_hint_cache();
let excerpt_id = hovered_inlay.position.excerpt_id;
// Extract the hint ID from the inlay
if let InlayId::Hint(_hint_id) = hovered_inlay.id {
if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_inlay.id) {
// Check if we should process this hint for hover
let should_process_hint = match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
// For unresolved hints, spawn resolution
if let Some(buffer_id) = previous_valid_anchor.buffer_id {
eprintln!("Spawning hint resolution for hint {:?}", hovered_hint.id);
if let Some(buffer_id) = hovered_inlay.position.buffer_id {
inlay_hint_cache.spawn_hint_resolve(
buffer_id,
excerpt_id,
hovered_hint.id,
hovered_inlay.id,
window,
cx,
);
@ -396,7 +350,6 @@ pub fn update_inlay_link_and_hover_points(
}
};
eprintln!("Should process hint: {}", should_process_hint);
if should_process_hint {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
@ -409,9 +362,7 @@ pub fn update_inlay_link_and_hover_points(
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
eprintln!("Processing String label hint");
if let Some(tooltip) = cached_hint.tooltip {
eprintln!("Found tooltip on main hint, calling hover_at_inlay");
hover_popover::hover_at_inlay(
editor,
InlayHover {
@ -428,10 +379,10 @@ pub fn update_inlay_link_and_hover_points(
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
inlay: hovered_inlay.id,
inlay_position: hovered_inlay.position,
range: extra_shift_left
..hovered_hint.text.len() + extra_shift_right,
..hovered_inlay.text.len() + extra_shift_right,
},
},
window,
@ -441,133 +392,263 @@ pub fn update_inlay_link_and_hover_points(
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
eprintln!(
"Processing LabelParts hint with {} parts",
label_parts.len()
);
let hint_start = snapshot.anchor_to_inlay_offset(hovered_hint.position);
eprintln!("Hint start offset: {:?}", hint_start);
eprintln!("Hovered offset: {:?}", hovered_offset);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts.clone(),
hint_start,
hovered_offset,
)
{
eprintln!("Found hovered hint part: {:?}", hovered_hint_part.value);
eprintln!(
"Part has tooltip: {}",
hovered_hint_part.tooltip.is_some()
);
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 {
eprintln!("Found tooltip on hint part, calling hover_at_inlay");
hover_popover::hover_at_inlay(
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(),
},
window,
cx,
);
hover_updated = true;
} else if let Some((_language_server_id, location)) =
&hovered_hint_part.location
{
// Fallback: Show location info when no tooltip is available
eprintln!(
"No tooltip, but has location. Showing type definition info"
);
// VS Code shows hover for the meaningful part regardless of where you hover
// Find the first part with actual hover information (tooltip or location)
let _hint_start =
snapshot.anchor_to_inlay_offset(hovered_inlay.position);
let mut part_offset = 0;
// Extract filename from the path
let filename =
location.uri.path().split('/').last().unwrap_or("unknown");
for part in label_parts {
let part_len = part.value.chars().count();
// Show information about where this type is defined
let hover_text = format!(
"{}\n\nDefined in {} at line {}",
hovered_hint_part.value.trim(),
filename,
location.range.start.line + 1
);
if part.tooltip.is_some() || part.location.is_some() {
// Found the meaningful part - show hover for it
let highlight_start = part_offset + extra_shift_left;
let highlight_end = part_offset + part_len + extra_shift_right;
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: HoverBlock {
text: hover_text,
kind: HoverBlockKind::Markdown,
},
range: highlight.clone(),
},
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,
let highlight = InlayHighlight {
inlay: hovered_inlay.id,
inlay_position: hovered_inlay.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = part.tooltip {
hover_popover::hover_at_inlay(
editor,
TriggerPoint::InlayHint(
highlight,
location,
language_server_id,
),
snapshot,
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(),
},
window,
cx,
);
hover_updated = true;
} else if let Some((_language_server_id, location)) =
part.location.clone()
{
// When there's no tooltip but we have a location, perform a "Go to Definition" style operation
// First show a loading message
let filename = location
.uri
.path()
.split('/')
.next_back()
.unwrap_or("unknown")
.to_string();
let loading_text = format!(
"{}\n\nLoading documentation from {}...",
part.value.trim(),
filename
);
eprintln!(
"Showing loading message for type: {}",
part.value.trim()
);
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: HoverBlock {
text: loading_text.clone(),
kind: HoverBlockKind::PlainText,
},
range: highlight.clone(),
},
window,
cx,
);
hover_updated = true;
// Now perform the "Go to Definition" flow to get hover documentation
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
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();
let lines: Vec<&str> = text.lines().collect();
// Look backwards from the definition line to find doc comments
let mut doc_lines = Vec::new();
let mut current_line = line_number.saturating_sub(1);
// Skip any attributes like #[derive(...)]
while current_line > 0 && lines.get(current_line).map_or(false, |line| {
let trimmed = line.trim();
trimmed.starts_with("#[") || trimmed.is_empty()
}) {
current_line = current_line.saturating_sub(1);
}
// Collect doc comments
while current_line > 0 {
if let Some(line) = lines.get(current_line) {
let trimmed = line.trim();
if trimmed.starts_with("///") {
// Remove the /// and any leading space
let doc_text = trimmed.strip_prefix("///").unwrap_or("")
.strip_prefix(" ").unwrap_or_else(|| trimmed.strip_prefix("///").unwrap_or(""));
doc_lines.push(doc_text.to_string());
} else if !trimmed.is_empty() {
// Stop at the first non-doc, non-empty line
break;
}
}
current_line = current_line.saturating_sub(1);
}
// Reverse to get correct order
doc_lines.reverse();
// Also get the actual definition line
let definition = lines.get(line_number)
.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::<String>());
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(
editor,
InlayHover {
tooltip: HoverBlock {
text: formatted_docs,
kind: HoverBlockKind::Markdown,
},
range: highlight,
},
window,
cx,
);
}).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 {}",
hint_value.trim(),
filename,
location.range.start.line + 1
);
editor.update_in(cx, |editor, window, cx| {
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: HoverBlock {
text: fallback_text,
kind: HoverBlockKind::PlainText,
},
range: highlight,
},
window,
cx,
);
}).log_err();
}
eprintln!("Documentation fetch complete");
anyhow::Ok(())
}
.log_err()
.await
}).detach();
}
}
if let Some((language_server_id, location)) = &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.clone(),
*language_server_id,
),
snapshot,
window,
cx,
);
}
}
// Found and processed the meaningful part
break;
}
} else {
eprintln!("No hovered hint part found");
part_offset += part_len;
}
}
};
}
} else {
eprintln!("No cached hint found for id {:?}", hovered_hint.id);
}
} else {
eprintln!("No inlay hint found at hovered offset");
}
} else {
eprintln!("No hovered offset calculated");
}
if !go_to_definition_updated {
editor.hide_hovered_link(cx)
}
if !hover_updated {
eprintln!("No hover was updated, calling hover_at with None");
hover_popover::hover_at(editor, None, window, cx);
}
}

View file

@ -132,10 +132,6 @@ pub fn hover_at_inlay(
window: &mut Window,
cx: &mut Context<Editor>,
) {
eprintln!(
"hover_at_inlay called - inlay_id: {:?}, range: {:?}",
inlay_hover.range.inlay, inlay_hover.range.range
);
if EditorSettings::get_global(cx).hover_popover_enabled {
if editor.pending_rename.is_some() {
return;
@ -163,18 +159,12 @@ pub fn hover_at_inlay(
}
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
eprintln!(
"hover_at_inlay: Creating task with {}ms delay",
hover_popover_delay
);
let task = cx.spawn_in(window, async move |this, cx| {
async move {
eprintln!("hover_at_inlay task: Starting delay");
cx.background_executor()
.timer(Duration::from_millis(hover_popover_delay))
.await;
eprintln!("hover_at_inlay task: Delay complete");
this.update(cx, |this, _| {
this.hover_state.diagnostic_popover = None;
})?;
@ -207,7 +197,6 @@ pub fn hover_at_inlay(
};
this.update(cx, |this, cx| {
eprintln!("hover_at_inlay task: Setting hover popover and calling notify");
// TODO: no background highlights happen for inlays currently
this.hover_state.info_popovers = vec![hover_popover];
cx.notify();
@ -220,7 +209,6 @@ pub fn hover_at_inlay(
});
editor.hover_state.info_task = Some(task);
eprintln!("hover_at_inlay: Task stored");
}
}