parent
0bb1a5f98a
commit
a509ae241a
2 changed files with 261 additions and 548 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue