aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs243
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 1use std::iter::once;
2//! source code items (e.g. function call, struct field, variable symbol...)
3 2
4use hir::{ 3use 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};
7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
9use ra_ide_db::{ 9use 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};
13use ra_syntax::{ 13use 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
20use crate::{ 15use 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};
24use itertools::Itertools;
25use 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.
62pub(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
65fn hover_text( 115fn 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
163pub(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
216fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { 217fn 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}