Skip to content

Commit 50617ee

Browse files
bors[bot]Veykril
andauthored
Merge #9856
9856: Show type actions on ranged type hover r=Veykril a=Veykril bors r+ Co-authored-by: Lukas Wirth <[email protected]>
2 parents 88311c4 + ec44388 commit 50617ee

File tree

3 files changed

+143
-58
lines changed

3 files changed

+143
-58
lines changed

crates/ide/src/fixture.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Utilities for creating `Analysis` instances for tests.
22
use ide_db::base_db::fixture::ChangeFixture;
3-
use test_utils::extract_annotations;
3+
use test_utils::{extract_annotations, RangeOrOffset};
44

55
use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
66

@@ -32,6 +32,15 @@ pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
3232
(host.analysis(), FileRange { file_id, range })
3333
}
3434

35+
/// Creates analysis for a single file, returns range marked with a pair of $0 or a position marked with $0.
36+
pub(crate) fn range_or_position(ra_fixture: &str) -> (Analysis, FileId, RangeOrOffset) {
37+
let mut host = AnalysisHost::default();
38+
let change_fixture = ChangeFixture::parse(ra_fixture);
39+
host.db.apply_change(change_fixture.change);
40+
let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
41+
(host.analysis(), file_id, range_or_offset)
42+
}
43+
3544
/// Creates analysis from a multi-file fixture, returns positions marked with $0.
3645
pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(FileRange, String)>) {
3746
let mut host = AnalysisHost::default();

crates/ide/src/hover.rs

Lines changed: 127 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use itertools::Itertools;
1313
use stdx::format_to;
1414
use syntax::{
1515
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
16-
SyntaxKind::*, SyntaxToken, T,
16+
SyntaxKind::*, SyntaxNode, SyntaxToken, T,
1717
};
1818

1919
use crate::{
@@ -54,6 +54,25 @@ pub enum HoverAction {
5454
GoToType(Vec<HoverGotoTypeData>),
5555
}
5656

57+
impl HoverAction {
58+
fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Self {
59+
let targets = targets
60+
.into_iter()
61+
.filter_map(|it| {
62+
Some(HoverGotoTypeData {
63+
mod_path: render_path(
64+
db,
65+
it.module(db)?,
66+
it.name(db).map(|name| name.to_string()),
67+
),
68+
nav: it.try_to_nav(db)?,
69+
})
70+
})
71+
.collect();
72+
HoverAction::GoToType(targets)
73+
}
74+
}
75+
5776
#[derive(Debug, Clone, Eq, PartialEq)]
5877
pub struct HoverGotoTypeData {
5978
pub mod_path: String,
@@ -81,28 +100,10 @@ pub(crate) fn hover(
81100
let sema = hir::Semantics::new(db);
82101
let file = sema.parse(file_id).syntax().clone();
83102

84-
let offset = if range.is_empty() {
85-
range.start()
86-
} else {
87-
let expr = file.covering_element(range).ancestors().find_map(|it| {
88-
match_ast! {
89-
match it {
90-
ast::Expr(expr) => Some(Either::Left(expr)),
91-
ast::Pat(pat) => Some(Either::Right(pat)),
92-
_ => None,
93-
}
94-
}
95-
})?;
96-
return hover_type_info(&sema, config, &expr).map(|it| {
97-
RangeInfo::new(
98-
match expr {
99-
Either::Left(it) => it.syntax().text_range(),
100-
Either::Right(it) => it.syntax().text_range(),
101-
},
102-
it,
103-
)
104-
});
105-
};
103+
if !range.is_empty() {
104+
return hover_ranged(&file, range, &sema, config);
105+
}
106+
let offset = range.start();
106107

107108
let token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
108109
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
@@ -112,8 +113,8 @@ pub(crate) fn hover(
112113
})?;
113114
let token = sema.descend_into_macros(token);
114115

116+
let mut range_override = None;
115117
let node = token.parent()?;
116-
let mut range = None;
117118
let definition = match_ast! {
118119
match node {
119120
// We don't use NameClass::referenced_or_defined here as we do not want to resolve
@@ -129,11 +130,13 @@ pub(crate) fn hover(
129130
}
130131
}),
131132
ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime).map_or_else(
132-
|| NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
133-
NameRefClass::Definition(it) => Some(it),
134-
_ => None,
135-
}),
136-
|d| d.defined(),
133+
|| {
134+
NameRefClass::classify_lifetime(&sema, &lifetime).and_then(|class| match class {
135+
NameRefClass::Definition(it) => Some(it),
136+
_ => None,
137+
})
138+
},
139+
NameClass::defined,
137140
),
138141
_ => {
139142
if ast::Comment::cast(token.clone()).is_some() {
@@ -145,7 +148,7 @@ pub(crate) fn hover(
145148
let mapped = doc_mapping.map(range)?;
146149
(mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
147150
})?;
148-
range = Some(idl_range);
151+
range_override = Some(idl_range);
149152
Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
150153
Either::Left(it) => Definition::ModuleDef(it),
151154
Either::Right(it) => Definition::Macro(it),
@@ -154,7 +157,7 @@ pub(crate) fn hover(
154157
if let res@Some(_) = try_hover_for_lint(&attr, &token) {
155158
return res;
156159
} else {
157-
range = Some(token.text_range());
160+
range_override = Some(token.text_range());
158161
try_resolve_derive_input_at(&sema, &attr, &token).map(Definition::Macro)
159162
}
160163
} else {
@@ -186,11 +189,11 @@ pub(crate) fn hover(
186189
res.actions.push(action);
187190
}
188191

189-
if let Some(action) = goto_type_action(db, definition) {
192+
if let Some(action) = goto_type_action_for_def(db, definition) {
190193
res.actions.push(action);
191194
}
192195

193-
let range = range.unwrap_or_else(|| sema.original_range(&node).range);
196+
let range = range_override.unwrap_or_else(|| sema.original_range(&node).range);
194197
return Some(RangeInfo::new(range, res));
195198
}
196199
}
@@ -199,6 +202,8 @@ pub(crate) fn hover(
199202
return res;
200203
}
201204

205+
// No definition below cursor, fall back to showing type hovers.
206+
202207
let node = token
203208
.ancestors()
204209
.take_while(|it| !ast::Item::can_cast(it.kind()))
@@ -220,6 +225,30 @@ pub(crate) fn hover(
220225
Some(RangeInfo::new(range, res))
221226
}
222227

228+
fn hover_ranged(
229+
file: &SyntaxNode,
230+
range: syntax::TextRange,
231+
sema: &Semantics<RootDatabase>,
232+
config: &HoverConfig,
233+
) -> Option<RangeInfo<HoverResult>> {
234+
let expr = file.covering_element(range).ancestors().find_map(|it| {
235+
match_ast! {
236+
match it {
237+
ast::Expr(expr) => Some(Either::Left(expr)),
238+
ast::Pat(pat) => Some(Either::Right(pat)),
239+
_ => None,
240+
}
241+
}
242+
})?;
243+
hover_type_info(sema, config, &expr).map(|it| {
244+
let range = match expr {
245+
Either::Left(it) => it.syntax().text_range(),
246+
Either::Right(it) => it.syntax().text_range(),
247+
};
248+
RangeInfo::new(range, it)
249+
})
250+
}
251+
223252
fn hover_type_info(
224253
sema: &Semantics<RootDatabase>,
225254
config: &HoverConfig,
@@ -231,7 +260,16 @@ fn hover_type_info(
231260
};
232261

233262
let mut res = HoverResult::default();
263+
let mut targets: Vec<hir::ModuleDef> = Vec::new();
264+
let mut push_new_def = |item: hir::ModuleDef| {
265+
if !targets.contains(&item) {
266+
targets.push(item);
267+
}
268+
};
269+
walk_and_push_ty(sema.db, &original, &mut push_new_def);
270+
234271
res.markup = if let Some(adjusted_ty) = adjusted {
272+
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
235273
let original = original.display(sema.db).to_string();
236274
let adjusted = adjusted_ty.display(sema.db).to_string();
237275
format!(
@@ -250,6 +288,7 @@ fn hover_type_info(
250288
original.display(sema.db).to_string().into()
251289
}
252290
};
291+
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
253292
Some(res)
254293
}
255294

@@ -354,7 +393,7 @@ fn runnable_action(
354393
}
355394
}
356395

357-
fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
396+
fn goto_type_action_for_def(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
358397
let mut targets: Vec<hir::ModuleDef> = Vec::new();
359398
let mut push_new_def = |item: hir::ModuleDef| {
360399
if !targets.contains(&item) {
@@ -372,30 +411,28 @@ fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
372411
_ => return None,
373412
};
374413

375-
ty.walk(db, |t| {
376-
if let Some(adt) = t.as_adt() {
377-
push_new_def(adt.into());
378-
} else if let Some(trait_) = t.as_dyn_trait() {
379-
push_new_def(trait_.into());
380-
} else if let Some(traits) = t.as_impl_traits(db) {
381-
traits.into_iter().for_each(|it| push_new_def(it.into()));
382-
} else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
383-
push_new_def(trait_.into());
384-
}
385-
});
414+
walk_and_push_ty(db, &ty, &mut push_new_def);
386415
}
387416

388-
let targets = targets
389-
.into_iter()
390-
.filter_map(|it| {
391-
Some(HoverGotoTypeData {
392-
mod_path: render_path(db, it.module(db)?, it.name(db).map(|name| name.to_string())),
393-
nav: it.try_to_nav(db)?,
394-
})
395-
})
396-
.collect();
417+
Some(HoverAction::goto_type_from_targets(db, targets))
418+
}
397419

398-
Some(HoverAction::GoToType(targets))
420+
fn walk_and_push_ty(
421+
db: &RootDatabase,
422+
ty: &hir::Type,
423+
push_new_def: &mut dyn FnMut(hir::ModuleDef),
424+
) {
425+
ty.walk(db, |t| {
426+
if let Some(adt) = t.as_adt() {
427+
push_new_def(adt.into());
428+
} else if let Some(trait_) = t.as_dyn_trait() {
429+
push_new_def(trait_.into());
430+
} else if let Some(traits) = t.as_impl_traits(db) {
431+
traits.into_iter().for_each(|it| push_new_def(it.into()));
432+
} else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
433+
push_new_def(trait_.into());
434+
}
435+
});
399436
}
400437

401438
fn hover_markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
@@ -666,14 +703,14 @@ mod tests {
666703
}
667704

668705
fn check_actions(ra_fixture: &str, expect: Expect) {
669-
let (analysis, position) = fixture::position(ra_fixture);
706+
let (analysis, file_id, position) = fixture::range_or_position(ra_fixture);
670707
let hover = analysis
671708
.hover(
672709
&HoverConfig {
673710
links_in_hover: true,
674711
documentation: Some(HoverDocFormat::Markdown),
675712
},
676-
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
713+
FileRange { file_id, range: position.range_or_empty() },
677714
)
678715
.unwrap()
679716
.unwrap();
@@ -4163,4 +4200,37 @@ fn foo() {
41634200
"#]],
41644201
);
41654202
}
4203+
4204+
#[test]
4205+
fn hover_range_shows_type_actions() {
4206+
check_actions(
4207+
r#"
4208+
struct Foo;
4209+
fn foo() {
4210+
let x: &Foo = $0&&&&&Foo$0;
4211+
}
4212+
"#,
4213+
expect![[r#"
4214+
[
4215+
GoToType(
4216+
[
4217+
HoverGotoTypeData {
4218+
mod_path: "test::Foo",
4219+
nav: NavigationTarget {
4220+
file_id: FileId(
4221+
0,
4222+
),
4223+
full_range: 0..11,
4224+
focus_range: 7..10,
4225+
name: "Foo",
4226+
kind: Struct,
4227+
description: "struct Foo",
4228+
},
4229+
},
4230+
],
4231+
),
4232+
]
4233+
"#]],
4234+
);
4235+
}
41664236
}

crates/test_utils/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ impl RangeOrOffset {
113113
RangeOrOffset::Offset(_) => panic!("expected a range but got an offset"),
114114
}
115115
}
116+
pub fn range_or_empty(self) -> TextRange {
117+
match self {
118+
RangeOrOffset::Range(range) => range,
119+
RangeOrOffset::Offset(offset) => TextRange::empty(offset),
120+
}
121+
}
116122
}
117123

118124
impl From<RangeOrOffset> for TextRange {

0 commit comments

Comments
 (0)