diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 1a9f6cc768b..ffd5278ecb6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -26,8 +26,8 @@ use hir_ty::{ autoderef, display::{HirDisplayError, HirFormatter}, expr::ExprValidator, - method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty, - TyDefId, TypeCtor, + method_resolution, ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, + TraitEnvironment, Ty, TyDefId, TypeCtor, }; use ra_db::{CrateId, CrateName, Edition, FileId}; use ra_prof::profile; @@ -186,6 +186,22 @@ impl ModuleDef { module.visibility_of(db, self) } + + pub fn name(self, db: &dyn HirDatabase) -> Option { + match self { + ModuleDef::Adt(it) => Some(it.name(db)), + ModuleDef::Trait(it) => Some(it.name(db)), + ModuleDef::Function(it) => Some(it.name(db)), + ModuleDef::EnumVariant(it) => Some(it.name(db)), + ModuleDef::TypeAlias(it) => Some(it.name(db)), + + ModuleDef::Module(it) => it.name(db), + ModuleDef::Const(it) => it.name(db), + ModuleDef::Static(it) => it.name(db), + + ModuleDef::BuiltinType(it) => Some(it.as_name()), + } + } } pub use hir_def::{ @@ -1359,6 +1375,27 @@ impl Type { Some(adt.into()) } + pub fn as_dyn_trait(&self) -> Option { + self.ty.value.dyn_trait().map(Into::into) + } + + pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option> { + self.ty.value.impl_trait_bounds(db).map(|it| { + it.into_iter() + .filter_map(|pred| match pred { + hir_ty::GenericPredicate::Implemented(trait_ref) => { + Some(Trait::from(trait_ref.trait_)) + } + _ => None, + }) + .collect() + }) + } + + pub fn as_associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option { + self.ty.value.associated_type_parent_trait(db).map(Into::into) + } + // FIXME: provide required accessors such that it becomes implementable from outside. pub fn is_equal_for_find_impls(&self, other: &Type) -> bool { match (&self.ty.value, &other.ty.value) { @@ -1380,6 +1417,80 @@ impl Type { ty: InEnvironment { value: ty, environment: self.ty.environment.clone() }, } } + + pub fn walk(&self, db: &dyn HirDatabase, mut cb: impl FnMut(Type)) { + // TypeWalk::walk for a Ty at first visits parameters and only after that the Ty itself. + // We need a different order here. + + fn walk_substs( + db: &dyn HirDatabase, + type_: &Type, + substs: &Substs, + cb: &mut impl FnMut(Type), + ) { + for ty in substs.iter() { + walk_type(db, &type_.derived(ty.clone()), cb); + } + } + + fn walk_bounds( + db: &dyn HirDatabase, + type_: &Type, + bounds: &[GenericPredicate], + cb: &mut impl FnMut(Type), + ) { + for pred in bounds { + match pred { + GenericPredicate::Implemented(trait_ref) => { + cb(type_.clone()); + walk_substs(db, type_, &trait_ref.substs, cb); + } + _ => (), + } + } + } + + fn walk_type(db: &dyn HirDatabase, type_: &Type, cb: &mut impl FnMut(Type)) { + let ty = type_.ty.value.strip_references(); + match ty { + Ty::Apply(ApplicationTy { ctor, parameters }) => { + match ctor { + TypeCtor::Adt(_) => { + cb(type_.derived(ty.clone())); + } + TypeCtor::AssociatedType(_) => { + if let Some(_) = ty.associated_type_parent_trait(db) { + cb(type_.derived(ty.clone())); + } + } + _ => (), + } + + // adt params, tuples, etc... + walk_substs(db, type_, parameters, cb); + } + Ty::Opaque(opaque_ty) => { + if let Some(bounds) = ty.impl_trait_bounds(db) { + walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb); + } + + walk_substs(db, type_, &opaque_ty.parameters, cb); + } + Ty::Placeholder(_) => { + if let Some(bounds) = ty.impl_trait_bounds(db) { + walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb); + } + } + Ty::Dyn(bounds) => { + walk_bounds(db, &type_.derived(ty.clone()), bounds.as_ref(), cb); + } + + _ => (), + } + } + + walk_type(db, self, &mut cb); + } } impl HirDisplay for Type { diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 2b9372b4b1b..f2223232492 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -73,6 +73,7 @@ pub use lower::{ pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; pub use chalk_ir::{BoundVar, DebruijnIndex}; +use itertools::Itertools; /// A type constructor or type name: this might be something like the primitive /// type `bool`, a struct like `Vec`, or things like function pointers or @@ -815,6 +816,11 @@ impl Ty { } } + /// If this is a `dyn Trait`, returns that trait. + pub fn dyn_trait(&self) -> Option { + self.dyn_trait_ref().map(|it| it.trait_) + } + fn builtin_deref(&self) -> Option { match self { Ty::Apply(a_ty) => match a_ty.ctor { @@ -867,13 +873,56 @@ impl Ty { } } - /// If this is a `dyn Trait`, returns that trait. - pub fn dyn_trait(&self) -> Option { + pub fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option> { match self { - Ty::Dyn(predicates) => predicates.iter().find_map(|pred| match pred { - GenericPredicate::Implemented(tr) => Some(tr.trait_), - _ => None, - }), + Ty::Opaque(opaque_ty) => { + let predicates = match opaque_ty.opaque_ty_id { + OpaqueTyId::ReturnTypeImplTrait(func, idx) => { + db.return_type_impl_traits(func).map(|it| { + let data = (*it) + .as_ref() + .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + data.clone().subst(&opaque_ty.parameters) + }) + } + }; + + predicates.map(|it| it.value) + } + Ty::Placeholder(id) => { + let generic_params = db.generic_params(id.parent); + let param_data = &generic_params.types[id.local_id]; + match param_data.provenance { + hir_def::generics::TypeParamProvenance::ArgumentImplTrait => { + let predicates = db + .generic_predicates_for_param(*id) + .into_iter() + .map(|pred| pred.value.clone()) + .collect_vec(); + + Some(predicates) + } + _ => None, + } + } + _ => None, + } + } + + pub fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::AssociatedType(type_alias_id), .. }) => { + match type_alias_id.lookup(db.upcast()).container { + AssocContainerId::TraitId(trait_id) => Some(trait_id), + _ => None, + } + } + Ty::Projection(projection_ty) => { + match projection_ty.associated_ty.lookup(db.upcast()).container { + AssocContainerId::TraitId(trait_id) => Some(trait_id), + _ => None, + } + } _ => None, } } @@ -1057,5 +1106,5 @@ pub struct ReturnTypeImplTraits { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct ReturnTypeImplTrait { - pub(crate) bounds: Binders>, + pub bounds: Binders>, } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index ad78b767120..d870e4cbce6 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -2,7 +2,7 @@ use std::iter::once; use hir::{ Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, - ModuleDef, ModuleSource, Semantics, + Module, ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -13,7 +13,9 @@ use ra_ide_db::{ use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ - display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, + display::{ + macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav, + }, runnables::runnable, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, }; @@ -24,19 +26,21 @@ pub struct HoverConfig { pub implementations: bool, pub run: bool, pub debug: bool, + pub goto_type_def: bool, } impl Default for HoverConfig { fn default() -> Self { - Self { implementations: true, run: true, debug: true } + Self { implementations: true, run: true, debug: true, goto_type_def: true } } } impl HoverConfig { - pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; + pub const NO_ACTIONS: Self = + Self { implementations: false, run: false, debug: false, goto_type_def: false }; pub fn any(&self) -> bool { - self.implementations || self.runnable() + self.implementations || self.runnable() || self.goto_type_def } pub fn none(&self) -> bool { @@ -52,6 +56,13 @@ impl HoverConfig { pub enum HoverAction { Runnable(Runnable), Implementaion(FilePosition), + GoToType(Vec), +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct HoverGotoTypeData { + pub mod_path: String, + pub nav: NavigationTarget, } /// Contains the results when hovering over an item @@ -138,6 +149,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Option { + match def { + Definition::Local(it) => { + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + + it.ty(db).walk(db, |t| { + if let Some(adt) = t.as_adt() { + push_new_def(adt.into()); + } else if let Some(trait_) = t.as_dyn_trait() { + push_new_def(trait_.into()); + } else if let Some(traits) = t.as_impl_traits(db) { + traits.into_iter().for_each(|it| push_new_def(it.into())); + } else if let Some(trait_) = t.as_associated_type_parent_trait(db) { + push_new_def(trait_.into()); + } + }); + + let targets = targets + .into_iter() + .filter_map(|it| { + Some(HoverGotoTypeData { + mod_path: mod_path(db, &it)?, + nav: it.try_to_nav(db)?, + }) + }) + .collect(); + + Some(HoverAction::GoToType(targets)) + } + _ => None, + } +} + fn hover_text( docs: Option, desc: Option, @@ -248,25 +301,31 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option .map(|name| name.to_string()) } -fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option { - let mod_path = def.module(db).map(|module| { - once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) - .chain( - module - .path_to_root(db) - .into_iter() - .rev() - .map(|it| it.name(db).map(|name| name.to_string())), - ) - .chain(once(definition_owner_name(db, def))) - .flatten() - .join("::") - }); - mod_path +fn determine_mod_path(db: &RootDatabase, module: Module, name: Option) -> String { + once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) + .chain( + module + .path_to_root(db) + .into_iter() + .rev() + .map(|it| it.name(db).map(|name| name.to_string())), + ) + .chain(once(name)) + .flatten() + .join("::") +} + +// returns None only for ModuleDef::BuiltinType +fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option { + Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string()))) +} + +fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option { + def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def))) } fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { - let mod_path = determine_mod_path(db, &def); + let mod_path = definition_mod_path(db, &def); return match def { Definition::Macro(it) => { let src = it.source(db); @@ -1310,4 +1369,1045 @@ fn func(foo: i32) { if true { <|>foo; }; } ] "###); } + + #[test] + fn test_hover_struct_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + struct S{ f1: u32 } + + fn main() { + let s<|>t = S{ f1:0 }; + } + ", + &["S"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..19, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 7..8, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_generic_struct_has_goto_type_actions() { + let (_, actions) = check_hover_result( + " + //- /main.rs + struct Arg(u32); + struct S{ f1: T } + + fn main() { + let s<|>t = S{ f1:Arg(0) }; + } + ", + &["S"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 17..37, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 24..25, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Arg", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..16, + name: "Arg", + kind: STRUCT_DEF, + focus_range: Some( + 7..10, + ), + container_name: None, + description: Some( + "struct Arg", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_generic_struct_has_flattened_goto_type_actions() { + let (_, actions) = check_hover_result( + " + //- /main.rs + struct Arg(u32); + struct S{ f1: T } + + fn main() { + let s<|>t = S{ f1: S{ f1: Arg(0) } }; + } + ", + &["S>"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 17..37, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 24..25, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Arg", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..16, + name: "Arg", + kind: STRUCT_DEF, + focus_range: Some( + 7..10, + ), + container_name: None, + description: Some( + "struct Arg", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_tuple_has_goto_type_actions() { + let (_, actions) = check_hover_result( + " + //- /main.rs + struct A(u32); + struct B(u32); + mod M { + pub struct C(u32); + } + + fn main() { + let s<|>t = (A(1), B(2), M::C(3) ); + } + ", + &["(A, B, C)"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "A", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..14, + name: "A", + kind: STRUCT_DEF, + focus_range: Some( + 7..8, + ), + container_name: None, + description: Some( + "struct A", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "B", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 15..29, + name: "B", + kind: STRUCT_DEF, + focus_range: Some( + 22..23, + ), + container_name: None, + description: Some( + "struct B", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "M::C", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 42..60, + name: "C", + kind: STRUCT_DEF, + focus_range: Some( + 53..54, + ), + container_name: None, + description: Some( + "pub struct C", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_return_impl_trait_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo {} + + fn foo() -> impl Foo {} + + fn main() { + let s<|>t = foo(); + } + ", + &["impl Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_generic_return_impl_trait_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo {} + struct S; + + fn foo() -> impl Foo {} + + fn main() { + let s<|>t = foo(); + } + ", + &["impl Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..25, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_return_impl_traits_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo {} + trait Bar {} + + fn foo() -> impl Foo + Bar {} + + fn main() { + let s<|>t = foo(); + } + ", + &["impl Foo + Bar"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 13..25, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 19..22, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_generic_return_impl_traits_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo {} + trait Bar {} + struct S1 {} + struct S2 {} + + fn foo() -> impl Foo + Bar {} + + fn main() { + let s<|>t = foo(); + } + ", + &["impl Foo + Bar"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..31, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 22..25, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S1", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 32..44, + name: "S1", + kind: STRUCT_DEF, + focus_range: Some( + 39..41, + ), + container_name: None, + description: Some( + "struct S1", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S2", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 45..57, + name: "S2", + kind: STRUCT_DEF, + focus_range: Some( + 52..54, + ), + container_name: None, + description: Some( + "struct S2", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_arg_impl_trait_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait Foo {} + fn foo(ar<|>g: &impl Foo) {} + ", + &["&impl Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_arg_impl_traits_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait Foo {} + trait Bar {} + struct S{} + + fn foo(ar<|>g: &impl Foo + Bar) {} + ", + &["&impl Foo + Bar"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Bar", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 13..28, + name: "Bar", + kind: TRAIT_DEF, + focus_range: Some( + 19..22, + ), + container_name: None, + description: Some( + "trait Bar", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 29..39, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 36..37, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_arg_generic_impl_trait_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait Foo {} + struct S {} + fn foo(ar<|>g: &impl Foo) {} + ", + &["&impl Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..27, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_dyn_return_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo {} + struct S; + impl Foo for S {} + + struct B{} + + fn foo() -> B {} + + fn main() { + let s<|>t = foo(); + } + ", + &["B"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "B", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 41..54, + name: "B", + kind: STRUCT_DEF, + focus_range: Some( + 48..49, + ), + container_name: None, + description: Some( + "struct B", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_dyn_arg_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait Foo {} + fn foo(ar<|>g: &dyn Foo) {} + ", + &["&dyn Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..12, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_generic_dyn_arg_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait Foo {} + struct S {} + fn foo(ar<|>g: &dyn Foo) {} + ", + &["&dyn Foo"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..15, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 16..27, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 23..24, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_goto_type_action_links_order() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait ImplTrait {} + trait DynTrait {} + struct B {} + struct S {} + + fn foo(a<|>rg: &impl ImplTrait>>>) {} + ", + &["&impl ImplTrait>>>"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "ImplTrait", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..21, + name: "ImplTrait", + kind: TRAIT_DEF, + focus_range: Some( + 6..15, + ), + container_name: None, + description: Some( + "trait ImplTrait", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "B", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 43..57, + name: "B", + kind: STRUCT_DEF, + focus_range: Some( + 50..51, + ), + container_name: None, + description: Some( + "struct B", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "DynTrait", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 22..42, + name: "DynTrait", + kind: TRAIT_DEF, + focus_range: Some( + 28..36, + ), + container_name: None, + description: Some( + "trait DynTrait", + ), + docs: None, + }, + }, + HoverGotoTypeData { + mod_path: "S", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 58..69, + name: "S", + kind: STRUCT_DEF, + focus_range: Some( + 65..66, + ), + container_name: None, + description: Some( + "struct S", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } + + #[test] + fn test_hover_associated_type_has_goto_type_action() { + let (_, actions) = check_hover_result( + " + //- /main.rs + trait Foo { + type Item; + fn get(self) -> Self::Item {} + } + + struct Bar{} + struct S{} + + impl Foo for S{ + type Item = Bar; + } + + fn test() -> impl Foo { + S{} + } + + fn main() { + let s<|>t = test().get(); + } + ", + &["Foo::Item"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "Foo", + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..62, + name: "Foo", + kind: TRAIT_DEF, + focus_range: Some( + 6..9, + ), + container_name: None, + description: Some( + "trait Foo", + ), + docs: None, + }, + }, + ], + ), + ] + "###); + } } diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 51dc1f041ca..be9ab62c0bb 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -66,7 +66,7 @@ pub use crate::{ display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, expand_macro::ExpandedMacro, folding_ranges::{Fold, FoldKind}, - hover::{HoverAction, HoverConfig, HoverResult}, + hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, runnables::{Runnable, RunnableKind, TestId}, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 0df7427cb97..aa2c4ae15dc 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -296,6 +296,7 @@ impl Config { set(value, "/hoverActions/implementations", &mut self.hover.implementations); set(value, "/hoverActions/run", &mut self.hover.run); set(value, "/hoverActions/debug", &mut self.hover.debug); + set(value, "/hoverActions/gotoTypeDef", &mut self.hover.goto_type_def); } else { self.hover = HoverConfig::NO_ACTIONS; } diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index b34b529b5ca..2d7e649d2dc 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -18,8 +18,8 @@ use lsp_types::{ TextDocumentIdentifier, Url, WorkspaceEdit, }; use ra_ide::{ - FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, - SearchScope, TextEdit, + FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, + RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, }; use ra_prof::profile; use ra_project_model::TargetKind; @@ -1150,6 +1150,23 @@ fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command { } } +fn goto_location_command(snap: &GlobalStateSnapshot, nav: &NavigationTarget) -> Option { + let value = if snap.config.client_caps.location_link { + let link = to_proto::location_link(snap, None, nav.clone()).ok()?; + to_value(link).ok()? + } else { + let range = FileRange { file_id: nav.file_id(), range: nav.range() }; + let location = to_proto::location(snap, range).ok()?; + to_value(location).ok()? + }; + + Some(Command { + title: nav.name().to_string(), + command: "rust-analyzer.gotoLocation".into(), + arguments: Some(vec![value]), + }) +} + fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { lsp_ext::CommandLink { tooltip: Some(tooltip), command } } @@ -1180,13 +1197,13 @@ fn show_impl_command_link( None } -fn to_runnable_action( +fn runnable_action_links( snap: &GlobalStateSnapshot, file_id: FileId, runnable: Runnable, ) -> Option { let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; - if should_skip_target(&runnable, cargo_spec.as_ref()) { + if !snap.config.hover.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { return None; } @@ -1208,6 +1225,26 @@ fn to_runnable_action( }) } +fn goto_type_action_links( + snap: &GlobalStateSnapshot, + nav_targets: &[HoverGotoTypeData], +) -> Option { + if !snap.config.hover.goto_type_def || nav_targets.is_empty() { + return None; + } + + Some(lsp_ext::CommandLinkGroup { + title: Some("Go to ".into()), + commands: nav_targets + .iter() + .filter_map(|it| { + goto_location_command(snap, &it.nav) + .map(|cmd| to_command_link(cmd, it.mod_path.clone())) + }) + .collect(), + }) +} + fn prepare_hover_actions( snap: &GlobalStateSnapshot, file_id: FileId, @@ -1221,7 +1258,8 @@ fn prepare_hover_actions( .iter() .filter_map(|it| match it { HoverAction::Implementaion(position) => show_impl_command_link(snap, position), - HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r.clone()), + HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()), + HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), }) .collect() } diff --git a/editors/code/package.json b/editors/code/package.json index 3acc375f672..e6ceb235fcf 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -510,6 +510,11 @@ "type": "boolean", "default": true }, + "rust-analyzer.hoverActions.gotoTypeDef": { + "markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", + "type": "boolean", + "default": true + }, "rust-analyzer.linkedProjects": { "markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format", "type": "array", diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 3e9c3aa0e59..48a25495fb9 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -353,6 +353,20 @@ export function applyActionGroup(_ctx: Ctx): Cmd { }; } +export function gotoLocation(ctx: Ctx): Cmd { + return async (locationLink: lc.LocationLink) => { + const client = ctx.client; + if (client) { + const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri); + let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange); + // collapse the range to a cursor position + range = range.with({ end: range.start }); + + await vscode.window.showTextDocument(uri, { selection: range }); + } + }; +} + export function resolveCodeAction(ctx: Ctx): Cmd { const client = ctx.client; return async (params: ra.ResolveCodeActionParams) => { diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index d8f0037d4c9..9591d4fe32c 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -117,7 +117,7 @@ export class Config { return { engine: this.get("debug.engine"), engineSettings: this.get("debug.engineSettings"), - openUpDebugPane: this.get("debug.openUpDebugPane"), + openDebugPane: this.get("debug.openDebugPane"), sourceFileMap: sourceFileMap }; } @@ -135,6 +135,9 @@ export class Config { return { enable: this.get("hoverActions.enable"), implementations: this.get("hoverActions.implementations"), + run: this.get("hoverActions.run"), + debug: this.get("hoverActions.debug"), + gotoTypeDef: this.get("hoverActions.gotoTypeDef"), }; } } diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index a0c9b3ab2e6..61c12dbe074 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -82,7 +82,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise