mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 13:48:50 +00:00
Merge pull request #18921 from Veykril/push-zwullmxomvsm
internal: Compute inlay hint text edits lazily
This commit is contained in:
commit
69ab0cfb48
9 changed files with 150 additions and 57 deletions
|
@ -1,6 +1,6 @@
|
|||
use std::{
|
||||
fmt::{self, Write},
|
||||
mem::take,
|
||||
mem::{self, take},
|
||||
};
|
||||
|
||||
use either::Either;
|
||||
|
@ -297,6 +297,17 @@ pub struct InlayHintsConfig {
|
|||
pub closing_brace_hints_min_lines: Option<usize>,
|
||||
pub fields_to_resolve: InlayFieldsToResolve,
|
||||
}
|
||||
impl InlayHintsConfig {
|
||||
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> {
|
||||
if self.fields_to_resolve.resolve_text_edits {
|
||||
Lazy::Lazy
|
||||
} else {
|
||||
let edit = finish();
|
||||
never!(edit.is_empty(), "inlay hint produced an empty text edit");
|
||||
Lazy::Computed(edit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct InlayFieldsToResolve {
|
||||
|
@ -408,12 +419,32 @@ pub struct InlayHint {
|
|||
/// The actual label to show in the inlay hint.
|
||||
pub label: InlayHintLabel,
|
||||
/// Text edit to apply when "accepting" this inlay hint.
|
||||
pub text_edit: Option<TextEdit>,
|
||||
pub text_edit: Option<Lazy<TextEdit>>,
|
||||
/// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the
|
||||
/// hint does not support resolving.
|
||||
pub resolve_parent: Option<TextRange>,
|
||||
}
|
||||
|
||||
/// A type signaling that a value is either computed, or is available for computation.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Lazy<T> {
|
||||
Computed(T),
|
||||
Lazy,
|
||||
}
|
||||
|
||||
impl<T> Lazy<T> {
|
||||
pub fn computed(self) -> Option<T> {
|
||||
match self {
|
||||
Lazy::Computed(it) => Some(it),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_lazy(&self) -> bool {
|
||||
matches!(self, Self::Lazy)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for InlayHint {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.range.hash(state);
|
||||
|
@ -422,7 +453,7 @@ impl std::hash::Hash for InlayHint {
|
|||
self.pad_right.hash(state);
|
||||
self.kind.hash(state);
|
||||
self.label.hash(state);
|
||||
self.text_edit.is_some().hash(state);
|
||||
mem::discriminant(&self.text_edit).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,10 +470,6 @@ impl InlayHint {
|
|||
resolve_parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn needs_resolve(&self) -> Option<TextRange> {
|
||||
self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash)]
|
||||
|
@ -503,10 +530,6 @@ impl InlayHintLabel {
|
|||
}
|
||||
self.parts.push(part);
|
||||
}
|
||||
|
||||
pub fn needs_resolve(&self) -> bool {
|
||||
self.parts.iter().any(|part| part.linked_location.is_some() || part.tooltip.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for InlayHintLabel {
|
||||
|
@ -725,19 +748,22 @@ fn hint_iterator(
|
|||
|
||||
fn ty_to_text_edit(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
config: &InlayHintsConfig,
|
||||
node_for_hint: &SyntaxNode,
|
||||
ty: &hir::Type,
|
||||
offset_to_insert: TextSize,
|
||||
prefix: String,
|
||||
) -> Option<TextEdit> {
|
||||
let scope = sema.scope(node_for_hint)?;
|
||||
prefix: impl Into<String>,
|
||||
) -> Option<Lazy<TextEdit>> {
|
||||
// FIXME: Limit the length and bail out on excess somehow?
|
||||
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
|
||||
|
||||
let mut builder = TextEdit::builder();
|
||||
builder.insert(offset_to_insert, prefix);
|
||||
builder.insert(offset_to_insert, rendered);
|
||||
Some(builder.finish())
|
||||
let rendered = sema
|
||||
.scope(node_for_hint)
|
||||
.and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
|
||||
Some(config.lazy_text_edit(|| {
|
||||
let mut builder = TextEdit::builder();
|
||||
builder.insert(offset_to_insert, prefix.into());
|
||||
builder.insert(offset_to_insert, rendered);
|
||||
builder.finish()
|
||||
}))
|
||||
}
|
||||
|
||||
fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
|
||||
|
@ -847,7 +873,7 @@ mod tests {
|
|||
|
||||
let edits = inlay_hints
|
||||
.into_iter()
|
||||
.filter_map(|hint| hint.text_edit)
|
||||
.filter_map(|hint| hint.text_edit?.computed())
|
||||
.reduce(|mut acc, next| {
|
||||
acc.union(next).expect("merging text edits failed");
|
||||
acc
|
||||
|
@ -867,7 +893,8 @@ mod tests {
|
|||
let (analysis, file_id) = fixture::file(ra_fixture);
|
||||
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
|
||||
|
||||
let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect();
|
||||
let edits: Vec<_> =
|
||||
inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect();
|
||||
|
||||
assert!(edits.is_empty(), "unexpected edits: {edits:?}");
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ pub(super) fn hints(
|
|||
return None;
|
||||
}
|
||||
if allow_edit {
|
||||
let edit = {
|
||||
let edit = Some(config.lazy_text_edit(|| {
|
||||
let mut b = TextEditBuilder::default();
|
||||
if let Some(pre) = &pre {
|
||||
b.insert(
|
||||
|
@ -198,14 +198,14 @@ pub(super) fn hints(
|
|||
);
|
||||
}
|
||||
b.finish()
|
||||
};
|
||||
}));
|
||||
match (&mut pre, &mut post) {
|
||||
(Some(pre), Some(post)) => {
|
||||
pre.text_edit = Some(edit.clone());
|
||||
post.text_edit = Some(edit);
|
||||
pre.text_edit = edit.clone();
|
||||
post.text_edit = edit;
|
||||
}
|
||||
(Some(pre), None) => pre.text_edit = Some(edit),
|
||||
(None, Some(post)) => post.text_edit = Some(edit),
|
||||
(Some(pre), None) => pre.text_edit = edit,
|
||||
(None, Some(post)) => post.text_edit = edit,
|
||||
(None, None) => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,13 +78,14 @@ pub(super) fn hints(
|
|||
let text_edit = if let Some(colon_token) = &type_ascriptable {
|
||||
ty_to_text_edit(
|
||||
sema,
|
||||
config,
|
||||
desc_pat.syntax(),
|
||||
&ty,
|
||||
colon_token
|
||||
.as_ref()
|
||||
.map_or_else(|| pat.syntax().text_range(), |t| t.text_range())
|
||||
.end(),
|
||||
if colon_token.is_some() { String::new() } else { String::from(": ") },
|
||||
if colon_token.is_some() { "" } else { ": " },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -99,17 +99,24 @@ pub(super) fn hints(
|
|||
}
|
||||
|
||||
if let hints @ [_, ..] = &mut acc[acc_base..] {
|
||||
let mut edit = TextEditBuilder::default();
|
||||
for h in &mut *hints {
|
||||
edit.insert(
|
||||
match h.position {
|
||||
InlayHintPosition::Before => h.range.start(),
|
||||
InlayHintPosition::After => h.range.end(),
|
||||
},
|
||||
h.label.parts.iter().map(|p| &*p.text).chain(h.pad_right.then_some(" ")).collect(),
|
||||
);
|
||||
}
|
||||
let edit = edit.finish();
|
||||
let edit = config.lazy_text_edit(|| {
|
||||
let mut edit = TextEditBuilder::default();
|
||||
for h in &mut *hints {
|
||||
edit.insert(
|
||||
match h.position {
|
||||
InlayHintPosition::Before => h.range.start(),
|
||||
InlayHintPosition::After => h.range.end(),
|
||||
},
|
||||
h.label
|
||||
.parts
|
||||
.iter()
|
||||
.map(|p| &*p.text)
|
||||
.chain(h.pad_right.then_some(" "))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
edit.finish()
|
||||
});
|
||||
hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone()));
|
||||
}
|
||||
|
||||
|
|
|
@ -52,13 +52,14 @@ pub(super) fn hints(
|
|||
let text_edit = if has_block_body {
|
||||
ty_to_text_edit(
|
||||
sema,
|
||||
config,
|
||||
closure.syntax(),
|
||||
&ty,
|
||||
arrow
|
||||
.as_ref()
|
||||
.map_or_else(|| param_list.syntax().text_range(), |t| t.text_range())
|
||||
.end(),
|
||||
if arrow.is_none() { String::from(" -> ") } else { String::new() },
|
||||
if arrow.is_none() { " -> " } else { "" },
|
||||
)
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -36,13 +36,14 @@ pub(super) fn enum_hints(
|
|||
return None;
|
||||
}
|
||||
for variant in enum_.variant_list()?.variants() {
|
||||
variant_hints(acc, sema, &enum_, &variant);
|
||||
variant_hints(acc, config, sema, &enum_, &variant);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn variant_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
config: &InlayHintsConfig,
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
enum_: &ast::Enum,
|
||||
variant: &ast::Variant,
|
||||
|
@ -88,7 +89,9 @@ fn variant_hints(
|
|||
},
|
||||
kind: InlayKind::Discriminant,
|
||||
label,
|
||||
text_edit: d.ok().map(|val| TextEdit::insert(range.start(), format!("{eq_} {val}"))),
|
||||
text_edit: d.ok().map(|val| {
|
||||
config.lazy_text_edit(|| TextEdit::insert(range.end(), format!("{eq_} {val}")))
|
||||
}),
|
||||
position: InlayHintPosition::After,
|
||||
pad_left: false,
|
||||
pad_right: false,
|
||||
|
@ -99,8 +102,10 @@ fn variant_hints(
|
|||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::expect;
|
||||
|
||||
use crate::inlay_hints::{
|
||||
tests::{check_with_config, DISABLED_CONFIG},
|
||||
tests::{check_edit, check_with_config, DISABLED_CONFIG},
|
||||
DiscriminantHints, InlayHintsConfig,
|
||||
};
|
||||
|
||||
|
@ -207,4 +212,33 @@ enum Enum {
|
|||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit() {
|
||||
check_edit(
|
||||
InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
|
||||
r#"
|
||||
#[repr(u8)]
|
||||
enum Enum {
|
||||
Variant(),
|
||||
Variant1,
|
||||
Variant2 {},
|
||||
Variant3,
|
||||
Variant5,
|
||||
Variant6,
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
#[repr(u8)]
|
||||
enum Enum {
|
||||
Variant() = 0,
|
||||
Variant1 = 1,
|
||||
Variant2 {} = 2,
|
||||
Variant3 = 3,
|
||||
Variant5 = 4,
|
||||
Variant6 = 5,
|
||||
}
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{InlayHint, InlayHintsConfig};
|
|||
pub(super) fn extern_block_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
extern_block: ast::ExternBlock,
|
||||
) -> Option<()> {
|
||||
|
@ -23,7 +23,9 @@ pub(super) fn extern_block_hints(
|
|||
pad_right: true,
|
||||
kind: crate::InlayKind::ExternUnsafety,
|
||||
label: crate::InlayHintLabel::from("unsafe"),
|
||||
text_edit: Some(TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())),
|
||||
text_edit: Some(config.lazy_text_edit(|| {
|
||||
TextEdit::insert(abi.syntax().text_range().start(), "unsafe ".to_owned())
|
||||
})),
|
||||
resolve_parent: Some(extern_block.syntax().text_range()),
|
||||
});
|
||||
Some(())
|
||||
|
@ -32,7 +34,7 @@ pub(super) fn extern_block_hints(
|
|||
pub(super) fn fn_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
fn_: &ast::Fn,
|
||||
extern_block: &ast::ExternBlock,
|
||||
|
@ -42,14 +44,14 @@ pub(super) fn fn_hints(
|
|||
return None;
|
||||
}
|
||||
let fn_ = fn_.fn_token()?;
|
||||
acc.push(item_hint(extern_block, fn_));
|
||||
acc.push(item_hint(config, extern_block, fn_));
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub(super) fn static_hints(
|
||||
acc: &mut Vec<InlayHint>,
|
||||
FamousDefs(_sema, _): &FamousDefs<'_, '_>,
|
||||
_config: &InlayHintsConfig,
|
||||
config: &InlayHintsConfig,
|
||||
_file_id: EditionedFileId,
|
||||
static_: &ast::Static,
|
||||
extern_block: &ast::ExternBlock,
|
||||
|
@ -59,11 +61,15 @@ pub(super) fn static_hints(
|
|||
return None;
|
||||
}
|
||||
let static_ = static_.static_token()?;
|
||||
acc.push(item_hint(extern_block, static_));
|
||||
acc.push(item_hint(config, extern_block, static_));
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint {
|
||||
fn item_hint(
|
||||
config: &InlayHintsConfig,
|
||||
extern_block: &ast::ExternBlock,
|
||||
token: SyntaxToken,
|
||||
) -> InlayHint {
|
||||
InlayHint {
|
||||
range: token.text_range(),
|
||||
position: crate::InlayHintPosition::Before,
|
||||
|
@ -71,7 +77,7 @@ fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint {
|
|||
pad_right: true,
|
||||
kind: crate::InlayKind::ExternUnsafety,
|
||||
label: crate::InlayHintLabel::from("unsafe"),
|
||||
text_edit: {
|
||||
text_edit: Some(config.lazy_text_edit(|| {
|
||||
let mut builder = TextEdit::builder();
|
||||
builder.insert(token.text_range().start(), "unsafe ".to_owned());
|
||||
if extern_block.unsafe_token().is_none() {
|
||||
|
@ -79,8 +85,8 @@ fn item_hint(extern_block: &ast::ExternBlock, token: SyntaxToken) -> InlayHint {
|
|||
builder.insert(abi.syntax().text_range().start(), "unsafe ".to_owned());
|
||||
}
|
||||
}
|
||||
Some(builder.finish())
|
||||
},
|
||||
builder.finish()
|
||||
})),
|
||||
resolve_parent: Some(extern_block.syntax().text_range()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ pub(super) fn hints(
|
|||
range: t.text_range(),
|
||||
kind: InlayKind::Lifetime,
|
||||
label: "'static".into(),
|
||||
text_edit: Some(TextEdit::insert(t.text_range().start(), "'static ".into())),
|
||||
text_edit: Some(config.lazy_text_edit(|| {
|
||||
TextEdit::insert(t.text_range().start(), "'static ".into())
|
||||
})),
|
||||
position: InlayHintPosition::After,
|
||||
pad_left: false,
|
||||
pad_right: true,
|
||||
|
|
|
@ -547,7 +547,18 @@ pub(crate) fn inlay_hint(
|
|||
file_id: FileId,
|
||||
mut inlay_hint: InlayHint,
|
||||
) -> Cancellable<lsp_types::InlayHint> {
|
||||
let resolve_range_and_hash = inlay_hint.needs_resolve().map(|range| {
|
||||
let hint_needs_resolve = |hint: &InlayHint| -> Option<TextRange> {
|
||||
hint.resolve_parent.filter(|_| {
|
||||
hint.text_edit.is_some()
|
||||
|| hint
|
||||
.label
|
||||
.parts
|
||||
.iter()
|
||||
.any(|part| part.linked_location.is_some() || part.tooltip.is_some())
|
||||
})
|
||||
};
|
||||
|
||||
let resolve_range_and_hash = hint_needs_resolve(&inlay_hint).map(|range| {
|
||||
(
|
||||
range,
|
||||
std::hash::BuildHasher::hash_one(
|
||||
|
@ -568,7 +579,11 @@ pub(crate) fn inlay_hint(
|
|||
something_to_resolve |= inlay_hint.text_edit.is_some();
|
||||
None
|
||||
} else {
|
||||
inlay_hint.text_edit.take().map(|it| text_edit_vec(line_index, it))
|
||||
inlay_hint
|
||||
.text_edit
|
||||
.take()
|
||||
.and_then(|it| it.computed())
|
||||
.map(|it| text_edit_vec(line_index, it))
|
||||
};
|
||||
let (label, tooltip) = inlay_hint_label(
|
||||
snap,
|
||||
|
|
Loading…
Reference in a new issue