Revert "wip"

This reverts commit 0bb1a5f98a.
This commit is contained in:
Richard Feldman 2025-07-11 12:00:43 -04:00
parent 0bb1a5f98a
commit a509ae241a
No known key found for this signature in database
2 changed files with 261 additions and 548 deletions

View file

@ -287,601 +287,320 @@ pub fn update_inlay_link_and_hover_points(
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
let mut go_to_definition_updated = false;
let mut hover_updated = false;
// For inlay hints, we need to use the exact position where the mouse is // For inlay hints, we need to use the exact position where the mouse is
// But we must clip it to valid bounds to avoid panics // 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 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 hovered_offset = snapshot.display_point_to_inlay_offset(clipped_point, Bias::Left);
let mut go_to_definition_updated = false;
let mut hover_updated = false;
// Get all visible inlay hints // Get all visible inlay hints
let visible_hints = editor.visible_inlay_hints(cx); let visible_hints = editor.visible_inlay_hints(cx);
if point_for_position.column_overshoot_after_line_end == 0 { // Find if we're hovering over an inlay hint
let hovered_offset = if let Some(hovered_inlay) = visible_hints.into_iter().find(|inlay| {
snapshot.display_point_to_inlay_offset(point_for_position.exact_unclipped, Bias::Left); // Only process hint inlays
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx); if !matches!(inlay.id, InlayId::Hint(_)) {
let previous_valid_anchor = buffer_snapshot.anchor_at( return false;
point_for_position.previous_valid.to_point(snapshot), }
Bias::Left,
); // Check if the hovered position falls within this inlay's display range
let next_valid_anchor = buffer_snapshot.anchor_at( let inlay_start = snapshot.anchor_to_inlay_offset(inlay.position);
point_for_position.next_valid.to_point(snapshot), let inlay_end = InlayOffset(inlay_start.0 + inlay.text.len());
Bias::Right,
); hovered_offset >= inlay_start && hovered_offset < inlay_end
if let Some(hovered_hint) = editor }) {
.visible_inlay_hints(cx) let inlay_hint_cache = editor.inlay_hint_cache();
.into_iter() let excerpt_id = hovered_inlay.position.excerpt_id;
.skip_while(|hint| {
hint.position // Extract the hint ID from the inlay
.cmp(&previous_valid_anchor, &buffer_snapshot) if let InlayId::Hint(_hint_id) = hovered_inlay.id {
.is_lt() 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
.take_while(|hint| { let should_process_hint = match cached_hint.resolve_state {
hint.position
.cmp(&next_valid_anchor, &buffer_snapshot)
.is_le()
})
.max_by_key(|hint| 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) {
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => { ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = previous_valid_anchor.buffer_id { // For unresolved hints, spawn resolution
if let Some(buffer_id) = hovered_inlay.position.buffer_id {
inlay_hint_cache.spawn_hint_resolve( inlay_hint_cache.spawn_hint_resolve(
buffer_id, buffer_id,
excerpt_id, excerpt_id,
hovered_hint.id, hovered_inlay.id,
window, window,
cx, cx,
); );
} }
false // Don't process unresolved hints
}
ResolveState::Resolved => true,
ResolveState::Resolving => false,
};
if cached_hint.resolve_state.is_resolved() { if should_process_hint {
let mut extra_shift_left = 0; let mut extra_shift_left = 0;
let mut extra_shift_right = 0; let mut extra_shift_right = 0;
if cached_hint.padding_left { if cached_hint.padding_left {
extra_shift_left += 1; extra_shift_left += 1;
extra_shift_right += 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_inlay.id,
inlay_position: hovered_inlay.position,
range: extra_shift_left
..hovered_inlay.text.len() + extra_shift_right,
},
},
window,
cx,
);
hover_updated = true;
} }
if cached_hint.padding_right { }
extra_shift_right += 1; project::InlayHintLabel::LabelParts(label_parts) => {
} // Find the first part with actual hover information (tooltip or location)
match cached_hint.label { let _hint_start =
project::InlayHintLabel::String(_) => { snapshot.anchor_to_inlay_offset(hovered_inlay.position);
if let Some(tooltip) = cached_hint.tooltip { let mut part_offset = 0;
for part in label_parts {
let part_len = part.value.chars().count();
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;
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( hover_popover::hover_at_inlay(
editor, editor,
InlayHover { InlayHover {
tooltip: match tooltip { tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock { InlayHintLabelPartTooltip::String(text) => {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock { HoverBlock {
text: content.value, text,
kind: content.kind, kind: HoverBlockKind::PlainText,
} }
} }
InlayHintLabelPartTooltip::MarkupContent(
content,
) => HoverBlock {
text: content.value,
kind: content.kind,
},
}, },
range: InlayHighlight { range: highlight.clone(),
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text.len()
+ extra_shift_right,
},
}, },
window, window,
cx, cx,
); );
hover_updated = true; hover_updated = true;
} } else if let Some((_language_server_id, location)) =
} part.location.clone()
project::InlayHintLabel::LabelParts(label_parts) => { {
// Find the first part with actual hover information (tooltip or location) // When there's no tooltip but we have a location, perform a "Go to Definition" style operation
let _hint_start = let filename = location
snapshot.anchor_to_inlay_offset(hovered_hint.position); .uri
let mut part_offset = 0; .path()
.split('/')
.next_back()
.unwrap_or("unknown")
.to_string();
for part in label_parts { hover_popover::hover_at_inlay(
let part_len = part.value.chars().count(); editor,
InlayHover {
if part.tooltip.is_some() || part.location.is_some() { tooltip: HoverBlock {
// Found the meaningful part - show hover for it text: "Loading documentation...".to_string(),
let highlight_start = part_offset + extra_shift_left;
let highlight_end =
part_offset + part_len + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = part.tooltip {
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)) =
part.location.clone()
{
// When there's no tooltip but we have a location, perform a "Go to Definition" style operation
let filename = location
.uri
.path()
.split('/')
.next_back()
.unwrap_or("unknown")
.to_string();
hover_popover::hover_at_inlay(
editor,
InlayHover {
tooltip: HoverBlock {
text: "Loading documentation..."
.to_string(),
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 {
// 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
let file_path = location.uri.to_file_path()
.map_err(|_| anyhow::anyhow!("Invalid file URL"))?;
// Open the definition file
let definition_buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(file_path, cx)
})?
.await?;
// Extract documentation directly from the source
let documentation = definition_buffer.update(cx, |buffer, _| {
let line_number = location.range.start.line as usize;
// 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());
if doc_lines.is_empty() {
None
} else {
let docs = doc_lines.join("\n");
Some((definition, docs))
}
})?;
if let Some((definition, docs)) = documentation {
// 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 {
// 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();
}
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,
);
}
}
break;
}
part_offset += part_len;
}
}
};
}
}
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;
}
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, kind: HoverBlockKind::PlainText,
}, },
InlayHintTooltip::MarkupContent(content) => { range: highlight.clone(),
HoverBlock {
text: content.value,
kind: content.kind,
}
}
}, },
range: InlayHighlight { window,
inlay: hovered_hint.id, cx,
inlay_position: hovered_hint.position, );
range: extra_shift_left hover_updated = true;
..hovered_hint.text.len() + extra_shift_right,
},
},
window,
cx,
);
hover_updated = true;
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
// 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_hint.position);
let mut part_offset = 0;
for part in label_parts { // Now perform the "Go to Definition" flow to get hover documentation
let part_len = part.value.chars().count(); if let Some(project) = editor.project.clone() {
let highlight = highlight.clone();
let hint_value = part.value.clone();
let location_uri = location.uri.clone();
if part.tooltip.is_some() || part.location.is_some() { cx.spawn_in(window, async move |editor, cx| {
// Found the meaningful part - show hover for it async move {
let highlight_start = part_offset + extra_shift_left; // Small delay to show the loading message first
let highlight_end = cx.background_executor()
part_offset + part_len + extra_shift_right; .timer(std::time::Duration::from_millis(50))
.await;
let highlight = InlayHighlight { // Convert LSP URL to file path
inlay: hovered_hint.id, let file_path = location.uri.to_file_path()
inlay_position: hovered_hint.position, .map_err(|_| anyhow::anyhow!("Invalid file URL"))?;
range: highlight_start..highlight_end,
};
if let Some(tooltip) = part.tooltip { // Open the definition file
hover_popover::hover_at_inlay( let definition_buffer = project
editor, .update(cx, |project, cx| {
InlayHover { project.open_local_buffer(file_path, cx)
tooltip: match tooltip { })?
InlayHintLabelPartTooltip::String(text) => { .await?;
HoverBlock {
text, // Extract documentation directly from the source
kind: HoverBlockKind::PlainText, let documentation = definition_buffer.update(cx, |buffer, _| {
} let line_number = location.range.start.line as usize;
// 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);
} }
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
let filename = location
.uri
.path()
.split('/')
.next_back()
.unwrap_or("unknown")
.to_string();
hover_popover::hover_at_inlay( // Collect doc comments
editor, while current_line > 0 {
InlayHover { if let Some(line) = lines.get(current_line) {
tooltip: HoverBlock {
text: "Loading documentation..."
.to_string(),
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 {
// 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
let file_path = location.uri.to_file_path()
.map_err(|_| anyhow::anyhow!("Invalid file URL"))?;
// Open the definition file
let definition_buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(file_path, cx)
})?
.await?;
// Extract documentation directly from the source
let documentation = definition_buffer.update(cx, |buffer, _| {
let line_number = location.range.start.line as usize;
// 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(); let trimmed = line.trim();
trimmed.starts_with("#[") || trimmed.is_empty() if trimmed.starts_with("///") {
}) { // Remove the /// and any leading space
current_line = current_line.saturating_sub(1); 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());
// Collect doc comments } else if !trimmed.is_empty() {
while current_line > 0 { // Stop at the first non-doc, non-empty line
if let Some(line) = lines.get(current_line) { break;
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);
} }
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());
if doc_lines.is_empty() {
None
} else {
let docs = doc_lines.join("\n");
Some((definition, docs))
}
})?;
if let Some((definition, docs)) = documentation {
// 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 {
// 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();
} }
anyhow::Ok(()) // 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());
if doc_lines.is_empty() {
None
} else {
let docs = doc_lines.join("\n");
Some((definition, docs))
}
})?;
if let Some((definition, docs)) = documentation {
// 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 {
// 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();
} }
.log_err()
.await
}).detach();
}
}
if let Some((language_server_id, location)) = &part.location anyhow::Ok(())
{ }
if secondary_held .log_err()
&& !editor.has_pending_nonempty_selection() .await
{ }).detach();
go_to_definition_updated = true;
show_link_definition(
shift_held,
editor,
TriggerPoint::InlayHint(
highlight,
location.clone(),
*language_server_id,
),
snapshot,
window,
cx,
);
}
} }
break;
} }
part_offset += part_len; 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,
);
}
}
break;
} }
part_offset += part_len;
} }
}; }
} };
} }
} }
} }

View file

@ -656,12 +656,6 @@ pub enum ResolveState {
Resolving, Resolving,
} }
impl ResolveState {
pub fn is_resolved(&self) -> bool {
self == &Self::Resolved
}
}
impl InlayHint { impl InlayHint {
pub fn text(&self) -> String { pub fn text(&self) -> String {
match &self.label { match &self.label {