diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 243 |
1 files changed, 173 insertions, 70 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 3e721dcca..9636cd0d6 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,28 +1,21 @@ | |||
1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
3 | 2 | ||
4 | use hir::{ | 3 | use hir::{ |
5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, |
6 | ModuleSource, Semantics, | 5 | ModuleDef, ModuleSource, Semantics, |
7 | }; | 6 | }; |
7 | use itertools::Itertools; | ||
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 10 | defs::{classify_name, classify_name_ref, Definition}, |
11 | RootDatabase, | 11 | RootDatabase, |
12 | }; | 12 | }; |
13 | use ra_syntax::{ | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | ast::{self, DocCommentsOwner}, | ||
15 | match_ast, AstNode, | ||
16 | SyntaxKind::*, | ||
17 | SyntaxToken, TokenAtOffset, | ||
18 | }; | ||
19 | 14 | ||
20 | use crate::{ | 15 | use crate::{ |
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 17 | FilePosition, RangeInfo, |
23 | }; | 18 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 19 | ||
27 | /// Contains the results when hovering over an item | 20 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 21 | #[derive(Debug, Default)] |
@@ -62,6 +55,63 @@ impl HoverResult { | |||
62 | } | 55 | } |
63 | } | 56 | } |
64 | 57 | ||
58 | // Feature: Hover | ||
59 | // | ||
60 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
61 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
62 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
63 | let sema = Semantics::new(db); | ||
64 | let file = sema.parse(position.file_id).syntax().clone(); | ||
65 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
66 | let token = sema.descend_into_macros(token); | ||
67 | |||
68 | let mut res = HoverResult::new(); | ||
69 | |||
70 | if let Some((node, name_kind)) = match_ast! { | ||
71 | match (token.parent()) { | ||
72 | ast::NameRef(name_ref) => { | ||
73 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
74 | }, | ||
75 | ast::Name(name) => { | ||
76 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
77 | }, | ||
78 | _ => None, | ||
79 | } | ||
80 | } { | ||
81 | let range = sema.original_range(&node).range; | ||
82 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
83 | |||
84 | if !res.is_empty() { | ||
85 | return Some(RangeInfo::new(range, res)); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | let node = token | ||
90 | .ancestors() | ||
91 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
92 | |||
93 | let ty = match_ast! { | ||
94 | match node { | ||
95 | ast::MacroCall(_it) => { | ||
96 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
97 | // (e.g expanding a builtin macro). So we give up here. | ||
98 | return None; | ||
99 | }, | ||
100 | ast::Expr(it) => { | ||
101 | sema.type_of_expr(&it) | ||
102 | }, | ||
103 | ast::Pat(it) => { | ||
104 | sema.type_of_pat(&it) | ||
105 | }, | ||
106 | _ => None, | ||
107 | } | ||
108 | }?; | ||
109 | |||
110 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
111 | let range = sema.original_range(&node).range; | ||
112 | Some(RangeInfo::new(range, res)) | ||
113 | } | ||
114 | |||
65 | fn hover_text( | 115 | fn hover_text( |
66 | docs: Option<String>, | 116 | docs: Option<String>, |
67 | desc: Option<String>, | 117 | desc: Option<String>, |
@@ -114,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
114 | return match def { | 164 | return match def { |
115 | Definition::Macro(it) => { | 165 | Definition::Macro(it) => { |
116 | let src = it.source(db); | 166 | let src = it.source(db); |
117 | hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) | 167 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
168 | hover_text(docs, Some(macro_label(&src.value)), mod_path) | ||
118 | } | 169 | } |
119 | Definition::Field(it) => { | 170 | Definition::Field(it) => { |
120 | let src = it.source(db); | 171 | let src = it.source(db); |
121 | match src.value { | 172 | match src.value { |
122 | FieldSource::Named(it) => { | 173 | FieldSource::Named(it) => { |
123 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 174 | let docs = Documentation::from_ast(&it).map(Into::into); |
175 | hover_text(docs, it.short_label(), mod_path) | ||
124 | } | 176 | } |
125 | _ => None, | 177 | _ => None, |
126 | } | 178 | } |
@@ -128,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
128 | Definition::ModuleDef(it) => match it { | 180 | Definition::ModuleDef(it) => match it { |
129 | ModuleDef::Module(it) => match it.definition_source(db).value { | 181 | ModuleDef::Module(it) => match it.definition_source(db).value { |
130 | ModuleSource::Module(it) => { | 182 | ModuleSource::Module(it) => { |
131 | hover_text(it.doc_comment_text(), it.short_label(), mod_path) | 183 | let docs = Documentation::from_ast(&it).map(Into::into); |
184 | hover_text(docs, it.short_label(), mod_path) | ||
132 | } | 185 | } |
133 | _ => None, | 186 | _ => None, |
134 | }, | 187 | }, |
@@ -153,66 +206,14 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
153 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> | 206 | fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> |
154 | where | 207 | where |
155 | D: HasSource<Ast = A>, | 208 | D: HasSource<Ast = A>, |
156 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | 209 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, |
157 | { | 210 | { |
158 | let src = def.source(db); | 211 | let src = def.source(db); |
159 | hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) | 212 | let docs = Documentation::from_ast(&src.value).map(Into::into); |
213 | hover_text(docs, src.value.short_label(), mod_path) | ||
160 | } | 214 | } |
161 | } | 215 | } |
162 | 216 | ||
163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
164 | let sema = Semantics::new(db); | ||
165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
167 | let token = sema.descend_into_macros(token); | ||
168 | |||
169 | let mut res = HoverResult::new(); | ||
170 | |||
171 | if let Some((node, name_kind)) = match_ast! { | ||
172 | match (token.parent()) { | ||
173 | ast::NameRef(name_ref) => { | ||
174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
175 | }, | ||
176 | ast::Name(name) => { | ||
177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
178 | }, | ||
179 | _ => None, | ||
180 | } | ||
181 | } { | ||
182 | let range = sema.original_range(&node).range; | ||
183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
184 | |||
185 | if !res.is_empty() { | ||
186 | return Some(RangeInfo::new(range, res)); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | let node = token | ||
191 | .ancestors() | ||
192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
193 | |||
194 | let ty = match_ast! { | ||
195 | match node { | ||
196 | ast::MacroCall(_it) => { | ||
197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
198 | // (e.g expanding a builtin macro). So we give up here. | ||
199 | return None; | ||
200 | }, | ||
201 | ast::Expr(it) => { | ||
202 | sema.type_of_expr(&it) | ||
203 | }, | ||
204 | ast::Pat(it) => { | ||
205 | sema.type_of_pat(&it) | ||
206 | }, | ||
207 | _ => None, | ||
208 | } | ||
209 | }?; | ||
210 | |||
211 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
212 | let range = sema.original_range(&node).range; | ||
213 | Some(RangeInfo::new(range, res)) | ||
214 | } | ||
215 | |||
216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 217 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 218 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 219 | fn priority(n: &SyntaxToken) -> usize { |
@@ -949,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
949 | &["mod my"], | 950 | &["mod my"], |
950 | ); | 951 | ); |
951 | } | 952 | } |
953 | |||
954 | #[test] | ||
955 | fn test_hover_struct_doc_comment() { | ||
956 | check_hover_result( | ||
957 | r#" | ||
958 | //- /lib.rs | ||
959 | /// bar docs | ||
960 | struct Bar; | ||
961 | |||
962 | fn foo() { | ||
963 | let bar = Ba<|>r; | ||
964 | } | ||
965 | "#, | ||
966 | &["struct Bar\n```\n___\n\nbar docs"], | ||
967 | ); | ||
968 | } | ||
969 | |||
970 | #[test] | ||
971 | fn test_hover_struct_doc_attr() { | ||
972 | check_hover_result( | ||
973 | r#" | ||
974 | //- /lib.rs | ||
975 | #[doc = "bar docs"] | ||
976 | struct Bar; | ||
977 | |||
978 | fn foo() { | ||
979 | let bar = Ba<|>r; | ||
980 | } | ||
981 | "#, | ||
982 | &["struct Bar\n```\n___\n\nbar docs"], | ||
983 | ); | ||
984 | } | ||
985 | |||
986 | #[test] | ||
987 | fn test_hover_struct_doc_attr_multiple_and_mixed() { | ||
988 | check_hover_result( | ||
989 | r#" | ||
990 | //- /lib.rs | ||
991 | /// bar docs 0 | ||
992 | #[doc = "bar docs 1"] | ||
993 | #[doc = "bar docs 2"] | ||
994 | struct Bar; | ||
995 | |||
996 | fn foo() { | ||
997 | let bar = Ba<|>r; | ||
998 | } | ||
999 | "#, | ||
1000 | &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], | ||
1001 | ); | ||
1002 | } | ||
1003 | |||
1004 | #[test] | ||
1005 | fn test_hover_macro_generated_struct_fn_doc_comment() { | ||
1006 | check_hover_result( | ||
1007 | r#" | ||
1008 | //- /lib.rs | ||
1009 | macro_rules! bar { | ||
1010 | () => { | ||
1011 | struct Bar; | ||
1012 | impl Bar { | ||
1013 | /// Do the foo | ||
1014 | fn foo(&self) {} | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | |||
1019 | bar!(); | ||
1020 | |||
1021 | fn foo() { | ||
1022 | let bar = Bar; | ||
1023 | bar.fo<|>o(); | ||
1024 | } | ||
1025 | "#, | ||
1026 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], | ||
1027 | ); | ||
1028 | } | ||
1029 | |||
1030 | #[test] | ||
1031 | fn test_hover_macro_generated_struct_fn_doc_attr() { | ||
1032 | check_hover_result( | ||
1033 | r#" | ||
1034 | //- /lib.rs | ||
1035 | macro_rules! bar { | ||
1036 | () => { | ||
1037 | struct Bar; | ||
1038 | impl Bar { | ||
1039 | #[doc = "Do the foo"] | ||
1040 | fn foo(&self) {} | ||
1041 | } | ||
1042 | } | ||
1043 | } | ||
1044 | |||
1045 | bar!(); | ||
1046 | |||
1047 | fn foo() { | ||
1048 | let bar = Bar; | ||
1049 | bar.fo<|>o(); | ||
1050 | } | ||
1051 | "#, | ||
1052 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], | ||
1053 | ); | ||
1054 | } | ||
952 | } | 1055 | } |