Keep already computed inlay hint properties instead of late resolving them

This commit is contained in:
Lukas Wirth 2025-01-21 12:32:11 +01:00
parent 1977aa99b0
commit f5b86e056b
5 changed files with 99 additions and 92 deletions

View File

@ -302,21 +302,21 @@ pub struct InlayHintsConfig {
} }
impl InlayHintsConfig { impl InlayHintsConfig {
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> { fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> LazyProperty<TextEdit> {
if self.fields_to_resolve.resolve_text_edits { if self.fields_to_resolve.resolve_text_edits {
Lazy::Lazy LazyProperty::Lazy
} else { } else {
let edit = finish(); let edit = finish();
never!(edit.is_empty(), "inlay hint produced an empty text edit"); never!(edit.is_empty(), "inlay hint produced an empty text edit");
Lazy::Computed(edit) LazyProperty::Computed(edit)
} }
} }
fn lazy_tooltip(&self, finish: impl FnOnce() -> InlayTooltip) -> Lazy<InlayTooltip> { fn lazy_tooltip(&self, finish: impl FnOnce() -> InlayTooltip) -> LazyProperty<InlayTooltip> {
if self.fields_to_resolve.resolve_hint_tooltip if self.fields_to_resolve.resolve_hint_tooltip
&& self.fields_to_resolve.resolve_label_tooltip && self.fields_to_resolve.resolve_label_tooltip
{ {
Lazy::Lazy LazyProperty::Lazy
} else { } else {
let tooltip = finish(); let tooltip = finish();
never!( never!(
@ -327,7 +327,7 @@ impl InlayHintsConfig {
.is_empty(), .is_empty(),
"inlay hint produced an empty tooltip" "inlay hint produced an empty tooltip"
); );
Lazy::Computed(tooltip) LazyProperty::Computed(tooltip)
} }
} }
@ -336,11 +336,11 @@ impl InlayHintsConfig {
fn lazy_location_opt( fn lazy_location_opt(
&self, &self,
finish: impl FnOnce() -> Option<FileRange>, finish: impl FnOnce() -> Option<FileRange>,
) -> Option<Lazy<FileRange>> { ) -> Option<LazyProperty<FileRange>> {
if self.fields_to_resolve.resolve_label_location { if self.fields_to_resolve.resolve_label_location {
Some(Lazy::Lazy) Some(LazyProperty::Lazy)
} else { } else {
finish().map(Lazy::Computed) finish().map(LazyProperty::Computed)
} }
} }
} }
@ -455,7 +455,7 @@ pub struct InlayHint {
/// The actual label to show in the inlay hint. /// The actual label to show in the inlay hint.
pub label: InlayHintLabel, pub label: InlayHintLabel,
/// Text edit to apply when "accepting" this inlay hint. /// Text edit to apply when "accepting" this inlay hint.
pub text_edit: Option<Lazy<TextEdit>>, pub text_edit: Option<LazyProperty<TextEdit>>,
/// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the /// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the
/// hint does not support resolving. /// hint does not support resolving.
pub resolve_parent: Option<TextRange>, pub resolve_parent: Option<TextRange>,
@ -463,15 +463,15 @@ pub struct InlayHint {
/// A type signaling that a value is either computed, or is available for computation. /// A type signaling that a value is either computed, or is available for computation.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Lazy<T> { pub enum LazyProperty<T> {
Computed(T), Computed(T),
Lazy, Lazy,
} }
impl<T> Lazy<T> { impl<T> LazyProperty<T> {
pub fn computed(self) -> Option<T> { pub fn computed(self) -> Option<T> {
match self { match self {
Lazy::Computed(it) => Some(it), LazyProperty::Computed(it) => Some(it),
_ => None, _ => None,
} }
} }
@ -522,8 +522,8 @@ pub struct InlayHintLabel {
impl InlayHintLabel { impl InlayHintLabel {
pub fn simple( pub fn simple(
s: impl Into<String>, s: impl Into<String>,
tooltip: Option<Lazy<InlayTooltip>>, tooltip: Option<LazyProperty<InlayTooltip>>,
linked_location: Option<Lazy<FileRange>>, linked_location: Option<LazyProperty<FileRange>>,
) -> InlayHintLabel { ) -> InlayHintLabel {
InlayHintLabel { InlayHintLabel {
parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }], parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
@ -607,10 +607,10 @@ pub struct InlayHintLabelPart {
/// refers to (not necessarily the location itself). /// refers to (not necessarily the location itself).
/// When setting this, no tooltip must be set on the containing hint, or VS Code will display /// When setting this, no tooltip must be set on the containing hint, or VS Code will display
/// them both. /// them both.
pub linked_location: Option<Lazy<FileRange>>, pub linked_location: Option<LazyProperty<FileRange>>,
/// The tooltip to show when hovering over the inlay hint, this may invoke other actions like /// The tooltip to show when hovering over the inlay hint, this may invoke other actions like
/// hover requests to show. /// hover requests to show.
pub tooltip: Option<Lazy<InlayTooltip>>, pub tooltip: Option<LazyProperty<InlayTooltip>>,
} }
impl std::hash::Hash for InlayHintLabelPart { impl std::hash::Hash for InlayHintLabelPart {
@ -624,7 +624,9 @@ impl std::hash::Hash for InlayHintLabelPart {
impl fmt::Debug for InlayHintLabelPart { impl fmt::Debug for InlayHintLabelPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self { text, linked_location: None, tooltip: None | Some(Lazy::Lazy) } => text.fmt(f), Self { text, linked_location: None, tooltip: None | Some(LazyProperty::Lazy) } => {
text.fmt(f)
}
Self { text, linked_location, tooltip } => f Self { text, linked_location, tooltip } => f
.debug_struct("InlayHintLabelPart") .debug_struct("InlayHintLabelPart")
.field("text", text) .field("text", text)
@ -632,8 +634,10 @@ impl fmt::Debug for InlayHintLabelPart {
.field( .field(
"tooltip", "tooltip",
&tooltip.as_ref().map_or("", |it| match it { &tooltip.as_ref().map_or("", |it| match it {
Lazy::Computed(InlayTooltip::String(it) | InlayTooltip::Markdown(it)) => it, LazyProperty::Computed(
Lazy::Lazy => "", InlayTooltip::String(it) | InlayTooltip::Markdown(it),
) => it,
LazyProperty::Lazy => "",
}), }),
) )
.finish(), .finish(),
@ -677,7 +681,7 @@ impl InlayHintLabelBuilder<'_> {
if !text.is_empty() { if !text.is_empty() {
self.result.parts.push(InlayHintLabelPart { self.result.parts.push(InlayHintLabelPart {
text, text,
linked_location: self.location.take().map(Lazy::Computed), linked_location: self.location.take().map(LazyProperty::Computed),
tooltip: None, tooltip: None,
}); });
} }
@ -797,7 +801,7 @@ fn ty_to_text_edit(
ty: &hir::Type, ty: &hir::Type,
offset_to_insert: TextSize, offset_to_insert: TextSize,
prefix: impl Into<String>, prefix: impl Into<String>,
) -> Option<Lazy<TextEdit>> { ) -> Option<LazyProperty<TextEdit>> {
// FIXME: Limit the length and bail out on excess somehow? // FIXME: Limit the length and bail out on excess somehow?
let rendered = sema let rendered = sema
.scope(node_for_hint) .scope(node_for_hint)

View File

@ -83,7 +83,7 @@ mod tests {
fixture, fixture,
inlay_hints::{ inlay_hints::{
tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG}, tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
Lazy, LazyProperty,
}, },
InlayHintsConfig, InlayHintsConfig,
}; };
@ -102,7 +102,7 @@ mod tests {
let (analysis, file_id) = fixture::file(ra_fixture); let (analysis, file_id) = fixture::file(ra_fixture);
let mut inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap(); let mut inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
inlay_hints.iter_mut().flat_map(|hint| &mut hint.label.parts).for_each(|hint| { inlay_hints.iter_mut().flat_map(|hint| &mut hint.label.parts).for_each(|hint| {
if let Some(Lazy::Computed(loc)) = &mut hint.linked_location { if let Some(LazyProperty::Computed(loc)) = &mut hint.linked_location {
loc.range = TextRange::empty(TextSize::from(0)); loc.range = TextRange::empty(TextSize::from(0));
} }
}); });

View File

@ -12,7 +12,7 @@ use syntax::{
}; };
use crate::{ use crate::{
inlay_hints::Lazy, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind, inlay_hints::LazyProperty, InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind,
}; };
pub(super) fn hints( pub(super) fn hints(
@ -143,7 +143,7 @@ pub(super) fn hints(
acc.push(InlayHint { acc.push(InlayHint {
range: closing_token.text_range(), range: closing_token.text_range(),
kind: InlayKind::ClosingBrace, kind: InlayKind::ClosingBrace,
label: InlayHintLabel::simple(label, None, linked_location.map(Lazy::Computed)), label: InlayHintLabel::simple(label, None, linked_location.map(LazyProperty::Computed)),
text_edit: None, text_edit: None,
position: InlayHintPosition::After, position: InlayHintPosition::After,
pad_left: true, pad_left: true,

View File

@ -91,7 +91,7 @@ pub use crate::{
inlay_hints::{ inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints,
GenericParameterHints, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, GenericParameterHints, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart,
InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, InlayHintPosition, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints, LazyProperty
}, },
join_lines::JoinLinesConfig, join_lines::JoinLinesConfig,
markup::Markup, markup::Markup,

View File

@ -11,8 +11,8 @@ use ide::{
Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve, Annotation, AnnotationKind, Assist, AssistKind, Cancellable, CompletionFieldsToResolve,
CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange, CompletionItem, CompletionItemKind, CompletionRelevance, Documentation, FileId, FileRange,
FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlOperator, HlPunct, HlRange, HlTag, Indel,
InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, Markup, InlayFieldsToResolve, InlayHint, InlayHintLabel, InlayHintLabelPart, InlayKind, LazyProperty,
NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp, Markup, NavigationTarget, ReferenceCategory, RenameError, Runnable, Severity, SignatureHelp,
SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize, SnippetEdit, SourceChange, StructureNodeKind, SymbolKind, TextEdit, TextRange, TextSize,
}; };
use ide_db::{assists, rust_doc::format_docs, FxHasher}; use ide_db::{assists, rust_doc::format_docs, FxHasher};
@ -549,12 +549,11 @@ pub(crate) fn inlay_hint(
) -> Cancellable<lsp_types::InlayHint> { ) -> Cancellable<lsp_types::InlayHint> {
let hint_needs_resolve = |hint: &InlayHint| -> Option<TextRange> { let hint_needs_resolve = |hint: &InlayHint| -> Option<TextRange> {
hint.resolve_parent.filter(|_| { hint.resolve_parent.filter(|_| {
hint.text_edit.is_some() hint.text_edit.as_ref().is_some_and(LazyProperty::is_lazy)
|| hint || hint.label.parts.iter().any(|part| {
.label part.linked_location.as_ref().is_some_and(LazyProperty::is_lazy)
.parts || part.tooltip.as_ref().is_some_and(LazyProperty::is_lazy)
.iter() })
.any(|part| part.linked_location.is_some() || part.tooltip.is_some())
}) })
}; };
@ -569,22 +568,21 @@ pub(crate) fn inlay_hint(
}); });
let mut something_to_resolve = false; let mut something_to_resolve = false;
let text_edits = if snap let text_edits = inlay_hint
.config .text_edit
.visual_studio_code_version() .take()
.is_none_or(|version| VersionReq::parse(">=1.86.0").unwrap().matches(version)) .and_then(|it| match it {
&& resolve_range_and_hash.is_some() LazyProperty::Computed(it) => Some(it),
&& fields_to_resolve.resolve_text_edits LazyProperty::Lazy => {
{ something_to_resolve |=
something_to_resolve |= inlay_hint.text_edit.is_some(); snap.config.visual_studio_code_version().is_none_or(|version| {
None VersionReq::parse(">=1.86.0").unwrap().matches(version)
} else { }) && resolve_range_and_hash.is_some()
inlay_hint && fields_to_resolve.resolve_text_edits;
.text_edit None
.take() }
.and_then(|it| it.computed()) })
.map(|it| text_edit_vec(line_index, it)) .map(|it| text_edit_vec(line_index, it));
};
let (label, tooltip) = inlay_hint_label( let (label, tooltip) = inlay_hint_label(
snap, snap,
fields_to_resolve, fields_to_resolve,
@ -637,22 +635,23 @@ fn inlay_hint_label(
let (label, tooltip) = match &*label.parts { let (label, tooltip) = match &*label.parts {
[InlayHintLabelPart { linked_location: None, .. }] => { [InlayHintLabelPart { linked_location: None, .. }] => {
let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap(); let InlayHintLabelPart { text, tooltip, .. } = label.parts.pop().unwrap();
let hint_tooltip = if needs_resolve && fields_to_resolve.resolve_hint_tooltip { let tooltip = tooltip.and_then(|it| match it {
*something_to_resolve |= tooltip.is_some(); LazyProperty::Computed(it) => Some(it),
None LazyProperty::Lazy => {
} else { *something_to_resolve |=
match tooltip.and_then(|it| it.computed()) { needs_resolve && fields_to_resolve.resolve_hint_tooltip;
Some(ide::InlayTooltip::String(s)) => { None
Some(lsp_types::InlayHintTooltip::String(s))
}
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
}))
}
None => None,
} }
});
let hint_tooltip = match tooltip {
Some(ide::InlayTooltip::String(s)) => Some(lsp_types::InlayHintTooltip::String(s)),
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintTooltip::MarkupContent(lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
}))
}
None => None,
}; };
(lsp_types::InlayHintLabel::String(text), hint_tooltip) (lsp_types::InlayHintLabel::String(text), hint_tooltip)
} }
@ -661,34 +660,38 @@ fn inlay_hint_label(
.parts .parts
.into_iter() .into_iter()
.map(|part| { .map(|part| {
let tooltip = if needs_resolve && fields_to_resolve.resolve_label_tooltip { let tooltip = part.tooltip.and_then(|it| match it {
*something_to_resolve |= part.tooltip.is_some(); LazyProperty::Computed(it) => Some(it),
None LazyProperty::Lazy => {
} else { *something_to_resolve |= fields_to_resolve.resolve_label_tooltip;
match part.tooltip.and_then(|it| it.computed()) { None
Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::String(s))
}
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
},
))
}
None => None,
} }
});
let tooltip = match tooltip {
Some(ide::InlayTooltip::String(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::String(s))
}
Some(ide::InlayTooltip::Markdown(s)) => {
Some(lsp_types::InlayHintLabelPartTooltip::MarkupContent(
lsp_types::MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: s,
},
))
}
None => None,
}; };
let location = if needs_resolve && fields_to_resolve.resolve_label_location { let location = part
*something_to_resolve |= part.linked_location.is_some(); .linked_location
None .and_then(|it| match it {
} else { LazyProperty::Computed(it) => Some(it),
part.linked_location LazyProperty::Lazy => {
.and_then(|it| it.computed()) *something_to_resolve |= fields_to_resolve.resolve_label_location;
.map(|range| location(snap, range)) None
.transpose()? }
}; })
.map(|range| location(snap, range))
.transpose()?;
Ok(lsp_types::InlayHintLabelPart { Ok(lsp_types::InlayHintLabelPart {
value: part.text, value: part.text,
tooltip, tooltip,