Skip to content

Commit 9a6a61a

Browse files
committed
internal: Compute inlay hint text edits lazily
1 parent 720e727 commit 9a6a61a

18 files changed

+139
-70
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
fmt::{self, Write},
3-
mem::take,
3+
mem::{self, take},
44
};
55

66
use either::Either;
@@ -297,6 +297,17 @@ pub struct InlayHintsConfig {
297297
pub closing_brace_hints_min_lines: Option<usize>,
298298
pub fields_to_resolve: InlayFieldsToResolve,
299299
}
300+
impl InlayHintsConfig {
301+
fn lazy_text_edit(&self, finish: impl FnOnce() -> TextEdit) -> Lazy<TextEdit> {
302+
if self.fields_to_resolve.resolve_text_edits {
303+
Lazy::Lazy
304+
} else {
305+
let edit = finish();
306+
never!(edit.is_empty(), "inlay hint produced an empty text edit");
307+
Lazy::Computed(edit)
308+
}
309+
}
310+
}
300311

301312
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
302313
pub struct InlayFieldsToResolve {
@@ -408,12 +419,39 @@ pub struct InlayHint {
408419
/// The actual label to show in the inlay hint.
409420
pub label: InlayHintLabel,
410421
/// Text edit to apply when "accepting" this inlay hint.
411-
pub text_edit: Option<TextEdit>,
422+
pub text_edit: Option<Lazy<TextEdit>>,
412423
/// Range to recompute inlay hints when trying to resolve for this hint. If this is none, the
413424
/// hint does not support resolving.
414425
pub resolve_parent: Option<TextRange>,
415426
}
416427

428+
/// A type signaling that a value is either computed, or is available for computation.
429+
#[derive(Clone, Debug)]
430+
pub enum Lazy<T> {
431+
Computed(T),
432+
Lazy,
433+
}
434+
435+
impl<T> Lazy<T> {
436+
pub fn computed(self) -> Option<T> {
437+
match self {
438+
Lazy::Computed(it) => Some(it),
439+
_ => None,
440+
}
441+
}
442+
443+
pub fn is_lazy(&self) -> bool {
444+
matches!(self, Self::Lazy)
445+
}
446+
447+
// pub fn take(&mut self) -> Option<T> {
448+
// match mem::replace(self, Self::None) {
449+
// Self::Computed(edit) => Some(edit),
450+
// _ => None,
451+
// }
452+
// }
453+
}
454+
417455
impl std::hash::Hash for InlayHint {
418456
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
419457
self.range.hash(state);
@@ -422,7 +460,7 @@ impl std::hash::Hash for InlayHint {
422460
self.pad_right.hash(state);
423461
self.kind.hash(state);
424462
self.label.hash(state);
425-
self.text_edit.is_some().hash(state);
463+
mem::discriminant(&self.text_edit).hash(state);
426464
}
427465
}
428466

@@ -439,10 +477,6 @@ impl InlayHint {
439477
resolve_parent: None,
440478
}
441479
}
442-
443-
pub fn needs_resolve(&self) -> Option<TextRange> {
444-
self.resolve_parent.filter(|_| self.text_edit.is_some() || self.label.needs_resolve())
445-
}
446480
}
447481

448482
#[derive(Debug, Hash)]
@@ -503,10 +537,6 @@ impl InlayHintLabel {
503537
}
504538
self.parts.push(part);
505539
}
506-
507-
pub fn needs_resolve(&self) -> bool {
508-
self.parts.iter().any(|part| part.linked_location.is_some() || part.tooltip.is_some())
509-
}
510540
}
511541

512542
impl From<String> for InlayHintLabel {
@@ -725,19 +755,22 @@ fn hint_iterator(
725755

726756
fn ty_to_text_edit(
727757
sema: &Semantics<'_, RootDatabase>,
758+
config: &InlayHintsConfig,
728759
node_for_hint: &SyntaxNode,
729760
ty: &hir::Type,
730761
offset_to_insert: TextSize,
731762
prefix: String,
732-
) -> Option<TextEdit> {
733-
let scope = sema.scope(node_for_hint)?;
763+
) -> Option<Lazy<TextEdit>> {
734764
// FIXME: Limit the length and bail out on excess somehow?
735-
let rendered = ty.display_source_code(scope.db, scope.module().into(), false).ok()?;
736-
737-
let mut builder = TextEdit::builder();
738-
builder.insert(offset_to_insert, prefix);
739-
builder.insert(offset_to_insert, rendered);
740-
Some(builder.finish())
765+
let rendered = sema
766+
.scope(node_for_hint)
767+
.and_then(|scope| ty.display_source_code(scope.db, scope.module().into(), false).ok())?;
768+
Some(config.lazy_text_edit(|| {
769+
let mut builder = TextEdit::builder();
770+
builder.insert(offset_to_insert, prefix);
771+
builder.insert(offset_to_insert, rendered);
772+
builder.finish()
773+
}))
741774
}
742775

743776
fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
@@ -847,7 +880,7 @@ mod tests {
847880

848881
let edits = inlay_hints
849882
.into_iter()
850-
.filter_map(|hint| hint.text_edit)
883+
.filter_map(|hint| hint.text_edit?.computed())
851884
.reduce(|mut acc, next| {
852885
acc.union(next).expect("merging text edits failed");
853886
acc
@@ -867,7 +900,8 @@ mod tests {
867900
let (analysis, file_id) = fixture::file(ra_fixture);
868901
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
869902

870-
let edits: Vec<_> = inlay_hints.into_iter().filter_map(|hint| hint.text_edit).collect();
903+
let edits: Vec<_> =
904+
inlay_hints.into_iter().filter_map(|hint| hint.text_edit?.computed()).collect();
871905

872906
assert!(edits.is_empty(), "unexpected edits: {edits:?}");
873907
}

crates/ide/src/inlay_hints/adjustment.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub(super) fn hints(
7575
pad_right: false,
7676
kind: InlayKind::Adjustment,
7777
label: InlayHintLabel::default(),
78-
text_edit: None,
78+
text_edit: Default::default(),
7979
resolve_parent: Some(range),
8080
};
8181
let mut post = InlayHint {
@@ -85,7 +85,7 @@ pub(super) fn hints(
8585
pad_right: false,
8686
kind: InlayKind::Adjustment,
8787
label: InlayHintLabel::default(),
88-
text_edit: None,
88+
text_edit: Default::default(),
8989
resolve_parent: Some(range),
9090
};
9191

@@ -183,7 +183,7 @@ pub(super) fn hints(
183183
return None;
184184
}
185185
if allow_edit {
186-
let edit = {
186+
let edit = Some(config.lazy_text_edit(|| {
187187
let mut b = TextEditBuilder::default();
188188
if let Some(pre) = &pre {
189189
b.insert(
@@ -198,14 +198,14 @@ pub(super) fn hints(
198198
);
199199
}
200200
b.finish()
201-
};
201+
}));
202202
match (&mut pre, &mut post) {
203203
(Some(pre), Some(post)) => {
204-
pre.text_edit = Some(edit.clone());
205-
post.text_edit = Some(edit);
204+
pre.text_edit = edit.clone();
205+
post.text_edit = edit;
206206
}
207-
(Some(pre), None) => pre.text_edit = Some(edit),
208-
(None, Some(post)) => post.text_edit = Some(edit),
207+
(Some(pre), None) => pre.text_edit = edit,
208+
(None, Some(post)) => post.text_edit = edit,
209209
(None, None) => (),
210210
}
211211
}

crates/ide/src/inlay_hints/bind_pat.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ pub(super) fn hints(
7878
let text_edit = if let Some(colon_token) = &type_ascriptable {
7979
ty_to_text_edit(
8080
sema,
81+
config,
8182
desc_pat.syntax(),
8283
&ty,
8384
colon_token

crates/ide/src/inlay_hints/binding_mode.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub(super) fn hints(
4040
range,
4141
kind: InlayKind::BindingMode,
4242
label: InlayHintLabel::default(),
43-
text_edit: None,
43+
text_edit: Default::default(),
4444
position: InlayHintPosition::Before,
4545
pad_left: false,
4646
pad_right: false,
@@ -75,7 +75,7 @@ pub(super) fn hints(
7575
range: pat.syntax().text_range(),
7676
kind: InlayKind::BindingMode,
7777
label: bm.into(),
78-
text_edit: None,
78+
text_edit: Default::default(),
7979
position: InlayHintPosition::Before,
8080
pad_left: false,
8181
pad_right: true,
@@ -99,17 +99,24 @@ pub(super) fn hints(
9999
}
100100

101101
if let hints @ [_, ..] = &mut acc[acc_base..] {
102-
let mut edit = TextEditBuilder::default();
103-
for h in &mut *hints {
104-
edit.insert(
105-
match h.position {
106-
InlayHintPosition::Before => h.range.start(),
107-
InlayHintPosition::After => h.range.end(),
108-
},
109-
h.label.parts.iter().map(|p| &*p.text).chain(h.pad_right.then_some(" ")).collect(),
110-
);
111-
}
112-
let edit = edit.finish();
102+
let edit = config.lazy_text_edit(|| {
103+
let mut edit = TextEditBuilder::default();
104+
for h in &mut *hints {
105+
edit.insert(
106+
match h.position {
107+
InlayHintPosition::Before => h.range.start(),
108+
InlayHintPosition::After => h.range.end(),
109+
},
110+
h.label
111+
.parts
112+
.iter()
113+
.map(|p| &*p.text)
114+
.chain(h.pad_right.then_some(" "))
115+
.collect(),
116+
);
117+
}
118+
edit.finish()
119+
});
113120
hints.iter_mut().for_each(|h| h.text_edit = Some(edit.clone()));
114121
}
115122

crates/ide/src/inlay_hints/bounds.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub(super) fn hints(
6060
}
6161
hint
6262
},
63-
text_edit: None,
63+
text_edit: Default::default(),
6464
position: InlayHintPosition::After,
6565
pad_left: c.is_some(),
6666
pad_right: has_bounds,

crates/ide/src/inlay_hints/chaining.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub(super) fn hints(
6363
range: expr.syntax().text_range(),
6464
kind: InlayKind::Chaining,
6565
label,
66-
text_edit: None,
66+
text_edit: Default::default(),
6767
position: InlayHintPosition::After,
6868
pad_left: true,
6969
pad_right: false,

crates/ide/src/inlay_hints/closing_brace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ pub(super) fn hints(
142142
range: closing_token.text_range(),
143143
kind: InlayKind::ClosingBrace,
144144
label: InlayHintLabel::simple(label, None, linked_location),
145-
text_edit: None,
145+
text_edit: Default::default(),
146146
position: InlayHintPosition::After,
147147
pad_left: true,
148148
pad_right: false,

crates/ide/src/inlay_hints/closure_captures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(super) fn hints(
4343
range,
4444
kind: InlayKind::ClosureCapture,
4545
label,
46-
text_edit: None,
46+
text_edit: Default::default(),
4747
position: InlayHintPosition::After,
4848
pad_left: false,
4949
pad_right: true,

crates/ide/src/inlay_hints/closure_ret.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub(super) fn hints(
5252
let text_edit = if has_block_body {
5353
ty_to_text_edit(
5454
sema,
55+
config,
5556
closure.syntax(),
5657
&ty,
5758
arrow

crates/ide/src/inlay_hints/discriminant.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ pub(super) fn enum_hints(
3636
return None;
3737
}
3838
for variant in enum_.variant_list()?.variants() {
39-
variant_hints(acc, sema, &enum_, &variant);
39+
variant_hints(acc, config, sema, &enum_, &variant);
4040
}
4141
Some(())
4242
}
4343

4444
fn variant_hints(
4545
acc: &mut Vec<InlayHint>,
46+
config: &InlayHintsConfig,
4647
sema: &Semantics<'_, RootDatabase>,
4748
enum_: &ast::Enum,
4849
variant: &ast::Variant,
@@ -88,7 +89,9 @@ fn variant_hints(
8889
},
8990
kind: InlayKind::Discriminant,
9091
label,
91-
text_edit: d.ok().map(|val| TextEdit::insert(range.start(), format!("{eq_} {val}"))),
92+
text_edit: d.ok().map(|val| {
93+
config.lazy_text_edit(|| TextEdit::insert(range.start(), format!("{eq_} {val}")))
94+
}),
9295
position: InlayHintPosition::After,
9396
pad_left: false,
9497
pad_right: false,

0 commit comments

Comments
 (0)