diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 2ebf46b5649..8d24bdcf4f9 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -11,7 +11,7 @@ base_db::FileRange, defs::Definition, helpers::{pick_best_token, FamousDefs}, - RootDatabase, + FxIndexSet, RootDatabase, }; use itertools::Itertools; use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T}; @@ -69,7 +69,7 @@ fn goto_type_from_targets(db: &RootDatabase, targets: Vec) -> Se } } -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct HoverGotoTypeData { pub mod_path: String, pub nav: NavigationTarget, @@ -136,11 +136,12 @@ pub(crate) fn hover( .flatten() .unique_by(|&(def, _)| def) .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config)) - .reduce(|mut acc, HoverResult { markup, actions }| { + .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { acc.actions.extend(actions); acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup)); acc }); + if result.is_none() { // fallbacks, show keywords or types if let Some(res) = render::keyword(sema, config, &original_token) { @@ -152,7 +153,10 @@ pub(crate) fn hover( return res; } } - result.map(|res| RangeInfo::new(original_token.text_range(), res)) + result.map(|mut res: HoverResult| { + res.actions = dedupe_or_merge_hover_actions(res.actions); + RangeInfo::new(original_token.text_range(), res) + }) } pub(crate) fn hover_for_definition( @@ -341,3 +345,43 @@ fn walk_and_push_ty( } }); } + +fn dedupe_or_merge_hover_actions(actions: Vec) -> Vec { + let mut deduped_actions = Vec::with_capacity(actions.len()); + let mut go_to_type_targets = FxIndexSet::default(); + + let mut seen_implementation = false; + let mut seen_reference = false; + let mut seen_runnable = false; + for action in actions { + match action { + HoverAction::GoToType(targets) => { + go_to_type_targets.extend(targets); + } + HoverAction::Implementation(..) => { + if !seen_implementation { + seen_implementation = true; + deduped_actions.push(action); + } + } + HoverAction::Reference(..) => { + if !seen_reference { + seen_reference = true; + deduped_actions.push(action); + } + } + HoverAction::Runnable(..) => { + if !seen_runnable { + seen_runnable = true; + deduped_actions.push(action); + } + } + }; + } + + if !go_to_type_targets.is_empty() { + deduped_actions.push(HoverAction::GoToType(go_to_type_targets.into_iter().collect())); + } + + deduped_actions +} diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 3b415c54fb1..5db6fd79749 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -173,7 +173,7 @@ fn abc() --- Outer - "#]], + "#]], ); } @@ -1135,6 +1135,39 @@ fn bar() -> bool ); } +#[test] +fn test_hover_multiple_actions() { + check_actions( + r#" +struct Bar; +struct Foo { bar: Bar } + +fn foo(Foo { b$0ar }: &Foo) {} + "#, + expect![[r#" + [ + GoToType( + [ + HoverGotoTypeData { + mod_path: "test::Bar", + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 0..11, + focus_range: 7..10, + name: "Bar", + kind: Struct, + description: "struct Bar", + }, + }, + ], + ), + ] + "#]], + ) +} + #[test] fn test_hover_through_literal_string_in_builtin_macro() { check_hover_no_result( @@ -1750,9 +1783,6 @@ fn foo_$0test() {} cfg: None, }, ), - GoToType( - [], - ), ] "#]], ); @@ -2749,21 +2779,21 @@ fn main() { } "#, expect![[r#" - *f* + *f* - ```rust - f: &i32 - ``` - --- + ```rust + f: &i32 + ``` + --- - ```rust - test::S - ``` + ```rust + test::S + ``` - ```rust - f: i32 - ``` - "#]], + ```rust + f: i32 + ``` + "#]], ); }