Skip to content

Commit 496db0b

Browse files
committed
Implement location link for type inlay hints
1 parent 4596847 commit 496db0b

File tree

3 files changed

+132
-21
lines changed

3 files changed

+132
-21
lines changed

crates/hir-ty/src/display.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use hir_def::{
1616
path::{Path, PathKind},
1717
type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef},
1818
visibility::Visibility,
19-
HasModule, ItemContainerId, Lookup, ModuleId, TraitId,
19+
HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId,
2020
};
2121
use hir_expand::{hygiene::Hygiene, name::Name};
2222
use itertools::Itertools;
@@ -35,16 +35,44 @@ use crate::{
3535
TraitRefExt, Ty, TyExt, TyKind, WhereClause,
3636
};
3737

38+
pub trait HirWrite: fmt::Write {
39+
fn start_location_link(&mut self, location: ModuleDefId);
40+
fn end_location_link(&mut self);
41+
}
42+
43+
// String will ignore link metadata
44+
impl HirWrite for String {
45+
fn start_location_link(&mut self, _: ModuleDefId) {}
46+
47+
fn end_location_link(&mut self) {}
48+
}
49+
50+
// `core::Formatter` will ignore metadata
51+
impl HirWrite for fmt::Formatter<'_> {
52+
fn start_location_link(&mut self, _: ModuleDefId) {}
53+
fn end_location_link(&mut self) {}
54+
}
55+
3856
pub struct HirFormatter<'a> {
3957
pub db: &'a dyn HirDatabase,
40-
fmt: &'a mut dyn fmt::Write,
58+
fmt: &'a mut dyn HirWrite,
4159
buf: String,
4260
curr_size: usize,
4361
pub(crate) max_size: Option<usize>,
4462
omit_verbose_types: bool,
4563
display_target: DisplayTarget,
4664
}
4765

66+
impl HirFormatter<'_> {
67+
fn start_location_link(&mut self, location: ModuleDefId) {
68+
self.fmt.start_location_link(location);
69+
}
70+
71+
fn end_location_link(&mut self) {
72+
self.fmt.end_location_link();
73+
}
74+
}
75+
4876
pub trait HirDisplay {
4977
fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError>;
5078

@@ -245,20 +273,26 @@ pub struct HirDisplayWrapper<'a, T> {
245273
display_target: DisplayTarget,
246274
}
247275

248-
impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
249-
where
250-
T: HirDisplay,
251-
{
252-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253-
match self.t.hir_fmt(&mut HirFormatter {
276+
impl<T: HirDisplay> HirDisplayWrapper<'_, T> {
277+
pub fn write_to<F: HirWrite>(&self, f: &mut F) -> Result<(), HirDisplayError> {
278+
self.t.hir_fmt(&mut HirFormatter {
254279
db: self.db,
255280
fmt: f,
256281
buf: String::with_capacity(20),
257282
curr_size: 0,
258283
max_size: self.max_size,
259284
omit_verbose_types: self.omit_verbose_types,
260285
display_target: self.display_target,
261-
}) {
286+
})
287+
}
288+
}
289+
290+
impl<'a, T> fmt::Display for HirDisplayWrapper<'a, T>
291+
where
292+
T: HirDisplay,
293+
{
294+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295+
match self.write_to(f) {
262296
Ok(()) => Ok(()),
263297
Err(HirDisplayError::FmtError) => Err(fmt::Error),
264298
Err(HirDisplayError::DisplaySourceCodeError(_)) => {
@@ -530,6 +564,7 @@ impl HirDisplay for Ty {
530564
}
531565
}
532566
TyKind::Adt(AdtId(def_id), parameters) => {
567+
f.start_location_link((*def_id).into());
533568
match f.display_target {
534569
DisplayTarget::Diagnostics | DisplayTarget::Test => {
535570
let name = match *def_id {
@@ -554,6 +589,7 @@ impl HirDisplay for Ty {
554589
}
555590
}
556591
}
592+
f.end_location_link();
557593

558594
if parameters.len(Interner) > 0 {
559595
let parameters_to_write = if f.display_target.is_source_code()

crates/hir/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,20 @@ pub use {
114114
path::{ModPath, PathKind},
115115
type_ref::{Mutability, TypeRef},
116116
visibility::Visibility,
117+
// FIXME: This is here since it is input of a method in `HirWrite`
118+
// and things outside of hir need to implement that trait. We probably
119+
// should move whole `hir_ty::display` to this crate so we will become
120+
// able to use `ModuleDef` or `Definition` instead of `ModuleDefId`.
121+
ModuleDefId,
117122
},
118123
hir_expand::{
119124
name::{known, Name},
120125
ExpandResult, HirFileId, InFile, MacroFile, Origin,
121126
},
122-
hir_ty::{display::HirDisplay, PointerCast, Safety},
127+
hir_ty::{
128+
display::{HirDisplay, HirWrite},
129+
PointerCast, Safety,
130+
},
123131
};
124132

125133
// These are negative re-exports: pub using these names is forbidden, they

crates/ide/src/inlay_hints.rs

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
use std::fmt;
1+
use std::{fmt, mem::take};
22

33
use either::Either;
44
use hir::{
5-
known, Adjust, AutoBorrow, Callable, HasVisibility, HirDisplay, Mutability, OverloadedDeref,
6-
PointerCast, Safety, Semantics, TypeInfo,
5+
known, Adjust, AutoBorrow, Callable, HasVisibility, HirDisplay, HirWrite, ModuleDef,
6+
ModuleDefId, Mutability, OverloadedDeref, PointerCast, Safety, Semantics, TypeInfo,
77
};
88
use ide_db::{
99
base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
1010
RootDatabase,
1111
};
1212
use itertools::Itertools;
13-
use stdx::to_lower_snake_case;
13+
use stdx::{never, to_lower_snake_case};
1414
use syntax::{
1515
ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
1616
match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
1717
TextSize, T,
1818
};
1919

20-
use crate::FileId;
20+
use crate::{navigation_target::TryToNav, FileId};
2121

2222
#[derive(Clone, Debug, PartialEq, Eq)]
2323
pub struct InlayHintsConfig {
@@ -86,6 +86,7 @@ pub enum InlayTooltip {
8686
HoverOffset(FileId, TextSize),
8787
}
8888

89+
#[derive(Default)]
8990
pub struct InlayHintLabel {
9091
pub parts: Vec<InlayHintLabelPart>,
9192
}
@@ -886,6 +887,51 @@ fn binding_mode_hints(
886887
Some(())
887888
}
888889

890+
#[derive(Debug)]
891+
struct InlayHintLabelBuilder<'a> {
892+
db: &'a RootDatabase,
893+
result: InlayHintLabel,
894+
last_part: String,
895+
location: Option<FileRange>,
896+
}
897+
898+
impl fmt::Write for InlayHintLabelBuilder<'_> {
899+
fn write_str(&mut self, s: &str) -> fmt::Result {
900+
self.last_part.write_str(s)
901+
}
902+
}
903+
904+
impl HirWrite for InlayHintLabelBuilder<'_> {
905+
fn start_location_link(&mut self, def: ModuleDefId) {
906+
if self.location.is_some() {
907+
never!("location link is already started");
908+
}
909+
self.make_new_part();
910+
let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
911+
let location =
912+
FileRange { file_id: location.file_id, range: location.focus_or_full_range() };
913+
self.location = Some(location);
914+
}
915+
916+
fn end_location_link(&mut self) {
917+
self.make_new_part();
918+
}
919+
}
920+
921+
impl InlayHintLabelBuilder<'_> {
922+
fn make_new_part(&mut self) {
923+
self.result.parts.push(InlayHintLabelPart {
924+
text: take(&mut self.last_part),
925+
linked_location: self.location.take(),
926+
});
927+
}
928+
929+
fn finish(mut self) -> InlayHintLabel {
930+
self.make_new_part();
931+
self.result
932+
}
933+
}
934+
889935
fn bind_pat_hints(
890936
acc: &mut Vec<InlayHint>,
891937
sema: &Semantics<'_, RootDatabase>,
@@ -907,18 +953,27 @@ fn bind_pat_hints(
907953

908954
let krate = sema.scope(desc_pat.syntax())?.krate();
909955
let famous_defs = FamousDefs(sema, krate);
956+
957+
let mut label_builder = InlayHintLabelBuilder {
958+
db: sema.db,
959+
last_part: String::new(),
960+
location: None,
961+
result: InlayHintLabel::default(),
962+
};
963+
910964
let label = hint_iterator(sema, &famous_defs, config, &ty);
911965

912966
let label = match label {
913-
Some(label) => label,
967+
Some(label) => label.into(),
914968
None => {
915-
let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
969+
let _ = ty.display_truncated(sema.db, config.max_length).write_to(&mut label_builder);
970+
let r = label_builder.finish();
916971
if config.hide_named_constructor_hints
917-
&& is_named_constructor(sema, pat, &ty_name).is_some()
972+
&& is_named_constructor(sema, pat, &r.to_string()).is_some()
918973
{
919974
return None;
920975
}
921-
ty_name
976+
r
922977
}
923978
};
924979

@@ -928,7 +983,7 @@ fn bind_pat_hints(
928983
None => pat.syntax().text_range(),
929984
},
930985
kind: InlayKind::TypeHint,
931-
label: label.into(),
986+
label,
932987
tooltip: pat
933988
.name()
934989
.map(|it| it.syntax().text_range())
@@ -2736,7 +2791,19 @@ fn main() {
27362791
range: 124..130,
27372792
kind: TypeHint,
27382793
label: [
2739-
"Struct",
2794+
"",
2795+
InlayHintLabelPart {
2796+
text: "Struct",
2797+
linked_location: Some(
2798+
FileRange {
2799+
file_id: FileId(
2800+
0,
2801+
),
2802+
range: 7..13,
2803+
},
2804+
),
2805+
},
2806+
"",
27402807
],
27412808
tooltip: Some(
27422809
HoverRanged(

0 commit comments

Comments
 (0)