Skip to content

Commit 2a1477e

Browse files
committed
Implement location link for type inlay hints
1 parent acd06de commit 2a1477e

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
@@ -112,12 +112,20 @@ pub use {
112112
path::{ModPath, PathKind},
113113
type_ref::{Mutability, TypeRef},
114114
visibility::Visibility,
115+
// FIXME: This is here since it is input of a method in `HirWrite`
116+
// and things outside of hir need to implement that trait. We probably
117+
// should move whole `hir_ty::display` to this crate so we will become
118+
// able to use `ModuleDef` or `Definition` instead of `ModuleDefId`.
119+
ModuleDefId,
115120
},
116121
hir_expand::{
117122
name::{known, Name},
118123
ExpandResult, HirFileId, InFile, MacroFile, Origin,
119124
},
120-
hir_ty::{display::HirDisplay, PointerCast, Safety},
125+
hir_ty::{
126+
display::{HirDisplay, HirWrite},
127+
PointerCast, Safety,
128+
},
121129
};
122130

123131
// 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
}
@@ -882,6 +883,51 @@ fn binding_mode_hints(
882883
Some(())
883884
}
884885

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

904950
let krate = sema.scope(desc_pat.syntax())?.krate();
905951
let famous_defs = FamousDefs(sema, krate);
952+
953+
let mut label_builder = InlayHintLabelBuilder {
954+
db: sema.db,
955+
last_part: String::new(),
956+
location: None,
957+
result: InlayHintLabel::default(),
958+
};
959+
906960
let label = hint_iterator(sema, &famous_defs, config, &ty);
907961

908962
let label = match label {
909-
Some(label) => label,
963+
Some(label) => label.into(),
910964
None => {
911-
let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
965+
let _ = ty.display_truncated(sema.db, config.max_length).write_to(&mut label_builder);
966+
let r = label_builder.finish();
912967
if config.hide_named_constructor_hints
913-
&& is_named_constructor(sema, pat, &ty_name).is_some()
968+
&& is_named_constructor(sema, pat, &r.to_string()).is_some()
914969
{
915970
return None;
916971
}
917-
ty_name
972+
r
918973
}
919974
};
920975

@@ -924,7 +979,7 @@ fn bind_pat_hints(
924979
None => pat.syntax().text_range(),
925980
},
926981
kind: InlayKind::TypeHint,
927-
label: label.into(),
982+
label,
928983
tooltip: pat
929984
.name()
930985
.map(|it| it.syntax().text_range())
@@ -2732,7 +2787,19 @@ fn main() {
27322787
range: 124..130,
27332788
kind: TypeHint,
27342789
label: [
2735-
"Struct",
2790+
"",
2791+
InlayHintLabelPart {
2792+
text: "Struct",
2793+
linked_location: Some(
2794+
FileRange {
2795+
file_id: FileId(
2796+
0,
2797+
),
2798+
range: 7..13,
2799+
},
2800+
),
2801+
},
2802+
"",
27362803
],
27372804
tooltip: Some(
27382805
HoverRanged(

0 commit comments

Comments
 (0)