diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 264 |
1 files changed, 189 insertions, 75 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index befa977c7..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 { |
@@ -405,7 +406,7 @@ mod tests { | |||
405 | }; | 406 | }; |
406 | } | 407 | } |
407 | "#, | 408 | "#, |
408 | &["Foo\nfield_a: u32"], | 409 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
409 | ); | 410 | ); |
410 | 411 | ||
411 | // Hovering over the field in the definition | 412 | // Hovering over the field in the definition |
@@ -422,7 +423,7 @@ mod tests { | |||
422 | }; | 423 | }; |
423 | } | 424 | } |
424 | "#, | 425 | "#, |
425 | &["Foo\nfield_a: u32"], | 426 | &["Foo\n```\n\n```rust\nfield_a: u32"], |
426 | ); | 427 | ); |
427 | } | 428 | } |
428 | 429 | ||
@@ -475,7 +476,7 @@ fn main() { | |||
475 | ", | 476 | ", |
476 | ); | 477 | ); |
477 | let hover = analysis.hover(position).unwrap().unwrap(); | 478 | let hover = analysis.hover(position).unwrap().unwrap(); |
478 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); | 479 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n```\n\n```rust\nSome")); |
479 | 480 | ||
480 | let (analysis, position) = single_file_with_position( | 481 | let (analysis, position) = single_file_with_position( |
481 | " | 482 | " |
@@ -503,8 +504,12 @@ fn main() { | |||
503 | "#, | 504 | "#, |
504 | &[" | 505 | &[" |
505 | Option | 506 | Option |
507 | ``` | ||
508 | |||
509 | ```rust | ||
506 | None | 510 | None |
507 | ``` | 511 | ``` |
512 | ___ | ||
508 | 513 | ||
509 | The None variant | 514 | The None variant |
510 | " | 515 | " |
@@ -524,8 +529,12 @@ The None variant | |||
524 | "#, | 529 | "#, |
525 | &[" | 530 | &[" |
526 | Option | 531 | Option |
532 | ``` | ||
533 | |||
534 | ```rust | ||
527 | Some | 535 | Some |
528 | ``` | 536 | ``` |
537 | ___ | ||
529 | 538 | ||
530 | The Some variant | 539 | The Some variant |
531 | " | 540 | " |
@@ -606,7 +615,10 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
606 | ", | 615 | ", |
607 | ); | 616 | ); |
608 | let hover = analysis.hover(position).unwrap().unwrap(); | 617 | let hover = analysis.hover(position).unwrap().unwrap(); |
609 | assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); | 618 | assert_eq!( |
619 | trim_markup_opt(hover.info.first()), | ||
620 | Some("wrapper::Thing\n```\n\n```rust\nfn new() -> Thing") | ||
621 | ); | ||
610 | } | 622 | } |
611 | 623 | ||
612 | #[test] | 624 | #[test] |
@@ -882,7 +894,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
882 | fo<|>o(); | 894 | fo<|>o(); |
883 | } | 895 | } |
884 | ", | 896 | ", |
885 | &["fn foo()\n```\n\n<- `\u{3000}` here"], | 897 | &["fn foo()\n```\n___\n\n<- `\u{3000}` here"], |
886 | ); | 898 | ); |
887 | } | 899 | } |
888 | 900 | ||
@@ -938,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
938 | &["mod my"], | 950 | &["mod my"], |
939 | ); | 951 | ); |
940 | } | 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 | } | ||
941 | } | 1055 | } |