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.rs411
1 files changed, 385 insertions, 26 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index d96cb5596..ad78b7671 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,8 +1,8 @@
1use std::iter::once; 1use std::iter::once;
2 2
3use hir::{ 3use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 4 Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay,
5 ModuleSource, Semantics, 5 ModuleDef, ModuleSource, Semantics,
6}; 6};
7use itertools::Itertools; 7use itertools::Itertools;
8use ra_db::SourceDatabase; 8use ra_db::SourceDatabase;
@@ -10,22 +10,55 @@ 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};
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, ToNav},
22 FilePosition, RangeInfo, 17 runnables::runnable,
18 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
23}; 19};
20use test_utils::mark;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct HoverConfig {
24 pub implementations: bool,
25 pub run: bool,
26 pub debug: bool,
27}
28
29impl Default for HoverConfig {
30 fn default() -> Self {
31 Self { implementations: true, run: true, debug: true }
32 }
33}
34
35impl HoverConfig {
36 pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false };
37
38 pub fn any(&self) -> bool {
39 self.implementations || self.runnable()
40 }
41
42 pub fn none(&self) -> bool {
43 !self.any()
44 }
45
46 pub fn runnable(&self) -> bool {
47 self.run || self.debug
48 }
49}
50
51#[derive(Debug, Clone)]
52pub enum HoverAction {
53 Runnable(Runnable),
54 Implementaion(FilePosition),
55}
24 56
25/// Contains the results when hovering over an item 57/// Contains the results when hovering over an item
26#[derive(Debug, Default)] 58#[derive(Debug, Default)]
27pub struct HoverResult { 59pub struct HoverResult {
28 results: Vec<String>, 60 results: Vec<String>,
61 actions: Vec<HoverAction>,
29} 62}
30 63
31impl HoverResult { 64impl HoverResult {
@@ -53,10 +86,20 @@ impl HoverResult {
53 &self.results 86 &self.results
54 } 87 }
55 88
89 pub fn actions(&self) -> &[HoverAction] {
90 &self.actions
91 }
92
93 pub fn push_action(&mut self, action: HoverAction) {
94 self.actions.push(action);
95 }
96
56 /// Returns the results converted into markup 97 /// Returns the results converted into markup
57 /// for displaying in a UI 98 /// for displaying in a UI
99 ///
100 /// Does not process actions!
58 pub fn to_markup(&self) -> String { 101 pub fn to_markup(&self) -> String {
59 self.results.join("\n\n---\n") 102 self.results.join("\n\n___\n")
60 } 103 }
61} 104}
62 105
@@ -87,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
87 res.extend(hover_text_from_name_kind(db, name_kind)); 130 res.extend(hover_text_from_name_kind(db, name_kind));
88 131
89 if !res.is_empty() { 132 if !res.is_empty() {
133 if let Some(action) = show_implementations_action(db, name_kind) {
134 res.push_action(action);
135 }
136
137 if let Some(action) = runnable_action(&sema, name_kind, position.file_id) {
138 res.push_action(action);
139 }
140
90 return Some(RangeInfo::new(range, res)); 141 return Some(RangeInfo::new(range, res));
91 } 142 }
92 } 143 }
@@ -117,6 +168,56 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
117 Some(RangeInfo::new(range, res)) 168 Some(RangeInfo::new(range, res))
118} 169}
119 170
171fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
172 fn to_action(nav_target: NavigationTarget) -> HoverAction {
173 HoverAction::Implementaion(FilePosition {
174 file_id: nav_target.file_id(),
175 offset: nav_target.range().start(),
176 })
177 }
178
179 match def {
180 Definition::ModuleDef(it) => match it {
181 ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
182 ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
183 ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
184 ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
185 _ => None,
186 },
187 _ => None,
188 }
189}
190
191fn runnable_action(
192 sema: &Semantics<RootDatabase>,
193 def: Definition,
194 file_id: FileId,
195) -> Option<HoverAction> {
196 match def {
197 Definition::ModuleDef(it) => match it {
198 ModuleDef::Module(it) => match it.definition_source(sema.db).value {
199 ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id)
200 .map(|it| HoverAction::Runnable(it)),
201 _ => None,
202 },
203 ModuleDef::Function(it) => {
204 let src = it.source(sema.db);
205 if src.file_id != file_id.into() {
206 mark::hit!(hover_macro_generated_struct_fn_doc_comment);
207 mark::hit!(hover_macro_generated_struct_fn_doc_attr);
208
209 return None;
210 }
211
212 runnable(&sema, src.value.syntax().clone(), file_id)
213 .map(|it| HoverAction::Runnable(it))
214 }
215 _ => None,
216 },
217 _ => None,
218 }
219}
220
120fn hover_text( 221fn hover_text(
121 docs: Option<String>, 222 docs: Option<String>,
122 desc: Option<String>, 223 desc: Option<String>,
@@ -169,13 +270,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
169 return match def { 270 return match def {
170 Definition::Macro(it) => { 271 Definition::Macro(it) => {
171 let src = it.source(db); 272 let src = it.source(db);
172 hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) 273 let docs = Documentation::from_ast(&src.value).map(Into::into);
274 hover_text(docs, Some(macro_label(&src.value)), mod_path)
173 } 275 }
174 Definition::Field(it) => { 276 Definition::Field(it) => {
175 let src = it.source(db); 277 let src = it.source(db);
176 match src.value { 278 match src.value {
177 FieldSource::Named(it) => { 279 FieldSource::Named(it) => {
178 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 280 let docs = Documentation::from_ast(&it).map(Into::into);
281 hover_text(docs, it.short_label(), mod_path)
179 } 282 }
180 _ => None, 283 _ => None,
181 } 284 }
@@ -183,7 +286,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
183 Definition::ModuleDef(it) => match it { 286 Definition::ModuleDef(it) => match it {
184 ModuleDef::Module(it) => match it.definition_source(db).value { 287 ModuleDef::Module(it) => match it.definition_source(db).value {
185 ModuleSource::Module(it) => { 288 ModuleSource::Module(it) => {
186 hover_text(it.doc_comment_text(), it.short_label(), mod_path) 289 let docs = Documentation::from_ast(&it).map(Into::into);
290 hover_text(docs, it.short_label(), mod_path)
187 } 291 }
188 _ => None, 292 _ => None,
189 }, 293 },
@@ -208,10 +312,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
208 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String> 312 fn from_def_source<A, D>(db: &RootDatabase, def: D, mod_path: Option<String>) -> Option<String>
209 where 313 where
210 D: HasSource<Ast = A>, 314 D: HasSource<Ast = A>,
211 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, 315 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner,
212 { 316 {
213 let src = def.source(db); 317 let src = def.source(db);
214 hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) 318 let docs = Documentation::from_ast(&src.value).map(Into::into);
319 hover_text(docs, src.value.short_label(), mod_path)
215 } 320 }
216} 321}
217 322
@@ -229,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
229 334
230#[cfg(test)] 335#[cfg(test)]
231mod tests { 336mod tests {
337 use super::*;
338 use insta::assert_debug_snapshot;
339
232 use ra_db::FileLoader; 340 use ra_db::FileLoader;
233 use ra_syntax::TextRange; 341 use ra_syntax::TextRange;
234 342
@@ -242,7 +350,15 @@ mod tests {
242 s.map(trim_markup) 350 s.map(trim_markup)
243 } 351 }
244 352
245 fn check_hover_result(fixture: &str, expected: &[&str]) -> String { 353 fn assert_impl_action(action: &HoverAction, position: u32) {
354 let offset = match action {
355 HoverAction::Implementaion(pos) => pos.offset,
356 it => panic!("Unexpected hover action: {:#?}", it),
357 };
358 assert_eq!(offset, position.into());
359 }
360
361 fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
246 let (analysis, position) = analysis_and_position(fixture); 362 let (analysis, position) = analysis_and_position(fixture);
247 let hover = analysis.hover(position).unwrap().unwrap(); 363 let hover = analysis.hover(position).unwrap().unwrap();
248 let mut results = Vec::from(hover.info.results()); 364 let mut results = Vec::from(hover.info.results());
@@ -257,7 +373,7 @@ mod tests {
257 assert_eq!(hover.info.len(), expected.len()); 373 assert_eq!(hover.info.len(), expected.len());
258 374
259 let content = analysis.db.file_text(position.file_id); 375 let content = analysis.db.file_text(position.file_id);
260 content[hover.range].to_string() 376 (content[hover.range].to_string(), hover.info.actions().to_vec())
261 } 377 }
262 378
263 fn check_hover_no_result(fixture: &str) { 379 fn check_hover_no_result(fixture: &str) {
@@ -458,7 +574,7 @@ struct Test<K, T = u8> {
458} 574}
459 575
460fn main() { 576fn main() {
461 let zz<|> = Test { t: 23, k: 33 }; 577 let zz<|> = Test { t: 23u8, k: 33 };
462}"#, 578}"#,
463 &["Test<i32, u8>"], 579 &["Test<i32, u8>"],
464 ); 580 );
@@ -747,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
747 863
748 #[test] 864 #[test]
749 fn test_hover_through_macro() { 865 fn test_hover_through_macro() {
750 let hover_on = check_hover_result( 866 let (hover_on, _) = check_hover_result(
751 " 867 "
752 //- /lib.rs 868 //- /lib.rs
753 macro_rules! id { 869 macro_rules! id {
@@ -768,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
768 884
769 #[test] 885 #[test]
770 fn test_hover_through_expr_in_macro() { 886 fn test_hover_through_expr_in_macro() {
771 let hover_on = check_hover_result( 887 let (hover_on, _) = check_hover_result(
772 " 888 "
773 //- /lib.rs 889 //- /lib.rs
774 macro_rules! id { 890 macro_rules! id {
@@ -786,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
786 902
787 #[test] 903 #[test]
788 fn test_hover_through_expr_in_macro_recursive() { 904 fn test_hover_through_expr_in_macro_recursive() {
789 let hover_on = check_hover_result( 905 let (hover_on, _) = check_hover_result(
790 " 906 "
791 //- /lib.rs 907 //- /lib.rs
792 macro_rules! id_deep { 908 macro_rules! id_deep {
@@ -807,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
807 923
808 #[test] 924 #[test]
809 fn test_hover_through_func_in_macro_recursive() { 925 fn test_hover_through_func_in_macro_recursive() {
810 let hover_on = check_hover_result( 926 let (hover_on, _) = check_hover_result(
811 " 927 "
812 //- /lib.rs 928 //- /lib.rs
813 macro_rules! id_deep { 929 macro_rules! id_deep {
@@ -831,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
831 947
832 #[test] 948 #[test]
833 fn test_hover_through_literal_string_in_macro() { 949 fn test_hover_through_literal_string_in_macro() {
834 let hover_on = check_hover_result( 950 let (hover_on, _) = check_hover_result(
835 r#" 951 r#"
836 //- /lib.rs 952 //- /lib.rs
837 macro_rules! arr { 953 macro_rules! arr {
@@ -850,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
850 966
851 #[test] 967 #[test]
852 fn test_hover_through_assert_macro() { 968 fn test_hover_through_assert_macro() {
853 let hover_on = check_hover_result( 969 let (hover_on, _) = check_hover_result(
854 r#" 970 r#"
855 //- /lib.rs 971 //- /lib.rs
856 #[rustc_builtin_macro] 972 #[rustc_builtin_macro]
@@ -926,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
926 1042
927 #[test] 1043 #[test]
928 fn test_hover_trait_show_qualifiers() { 1044 fn test_hover_trait_show_qualifiers() {
929 check_hover_result( 1045 let (_, actions) = check_hover_result(
930 " 1046 "
931 //- /lib.rs 1047 //- /lib.rs
932 unsafe trait foo<|>() {} 1048 unsafe trait foo<|>() {}
933 ", 1049 ",
934 &["unsafe trait foo"], 1050 &["unsafe trait foo"],
935 ); 1051 );
1052 assert_impl_action(&actions[0], 13);
936 } 1053 }
937 1054
938 #[test] 1055 #[test]
@@ -951,4 +1068,246 @@ fn func(foo: i32) { if true { <|>foo; }; }
951 &["mod my"], 1068 &["mod my"],
952 ); 1069 );
953 } 1070 }
1071
1072 #[test]
1073 fn test_hover_struct_doc_comment() {
1074 check_hover_result(
1075 r#"
1076 //- /lib.rs
1077 /// bar docs
1078 struct Bar;
1079
1080 fn foo() {
1081 let bar = Ba<|>r;
1082 }
1083 "#,
1084 &["struct Bar\n```\n___\n\nbar docs"],
1085 );
1086 }
1087
1088 #[test]
1089 fn test_hover_struct_doc_attr() {
1090 check_hover_result(
1091 r#"
1092 //- /lib.rs
1093 #[doc = "bar docs"]
1094 struct Bar;
1095
1096 fn foo() {
1097 let bar = Ba<|>r;
1098 }
1099 "#,
1100 &["struct Bar\n```\n___\n\nbar docs"],
1101 );
1102 }
1103
1104 #[test]
1105 fn test_hover_struct_doc_attr_multiple_and_mixed() {
1106 check_hover_result(
1107 r#"
1108 //- /lib.rs
1109 /// bar docs 0
1110 #[doc = "bar docs 1"]
1111 #[doc = "bar docs 2"]
1112 struct Bar;
1113
1114 fn foo() {
1115 let bar = Ba<|>r;
1116 }
1117 "#,
1118 &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"],
1119 );
1120 }
1121
1122 #[test]
1123 fn test_hover_macro_generated_struct_fn_doc_comment() {
1124 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1125
1126 check_hover_result(
1127 r#"
1128 //- /lib.rs
1129 macro_rules! bar {
1130 () => {
1131 struct Bar;
1132 impl Bar {
1133 /// Do the foo
1134 fn foo(&self) {}
1135 }
1136 }
1137 }
1138
1139 bar!();
1140
1141 fn foo() {
1142 let bar = Bar;
1143 bar.fo<|>o();
1144 }
1145 "#,
1146 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"],
1147 );
1148 }
1149
1150 #[test]
1151 fn test_hover_macro_generated_struct_fn_doc_attr() {
1152 mark::check!(hover_macro_generated_struct_fn_doc_attr);
1153
1154 check_hover_result(
1155 r#"
1156 //- /lib.rs
1157 macro_rules! bar {
1158 () => {
1159 struct Bar;
1160 impl Bar {
1161 #[doc = "Do the foo"]
1162 fn foo(&self) {}
1163 }
1164 }
1165 }
1166
1167 bar!();
1168
1169 fn foo() {
1170 let bar = Bar;
1171 bar.fo<|>o();
1172 }
1173 "#,
1174 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
1175 );
1176 }
1177
1178 #[test]
1179 fn test_hover_trait_has_impl_action() {
1180 let (_, actions) = check_hover_result(
1181 "
1182 //- /lib.rs
1183 trait foo<|>() {}
1184 ",
1185 &["trait foo"],
1186 );
1187 assert_impl_action(&actions[0], 6);
1188 }
1189
1190 #[test]
1191 fn test_hover_struct_has_impl_action() {
1192 let (_, actions) = check_hover_result(
1193 "
1194 //- /lib.rs
1195 struct foo<|>() {}
1196 ",
1197 &["struct foo"],
1198 );
1199 assert_impl_action(&actions[0], 7);
1200 }
1201
1202 #[test]
1203 fn test_hover_union_has_impl_action() {
1204 let (_, actions) = check_hover_result(
1205 "
1206 //- /lib.rs
1207 union foo<|>() {}
1208 ",
1209 &["union foo"],
1210 );
1211 assert_impl_action(&actions[0], 6);
1212 }
1213
1214 #[test]
1215 fn test_hover_enum_has_impl_action() {
1216 let (_, actions) = check_hover_result(
1217 "
1218 //- /lib.rs
1219 enum foo<|>() {
1220 A,
1221 B
1222 }
1223 ",
1224 &["enum foo"],
1225 );
1226 assert_impl_action(&actions[0], 5);
1227 }
1228
1229 #[test]
1230 fn test_hover_test_has_action() {
1231 let (_, actions) = check_hover_result(
1232 "
1233 //- /lib.rs
1234 #[test]
1235 fn foo_<|>test() {}
1236 ",
1237 &["fn foo_test()"],
1238 );
1239 assert_debug_snapshot!(actions,
1240 @r###"
1241 [
1242 Runnable(
1243 Runnable {
1244 nav: NavigationTarget {
1245 file_id: FileId(
1246 1,
1247 ),
1248 full_range: 0..24,
1249 name: "foo_test",
1250 kind: FN_DEF,
1251 focus_range: Some(
1252 11..19,
1253 ),
1254 container_name: None,
1255 description: None,
1256 docs: None,
1257 },
1258 kind: Test {
1259 test_id: Path(
1260 "foo_test",
1261 ),
1262 attr: TestAttr {
1263 ignore: false,
1264 },
1265 },
1266 cfg_exprs: [],
1267 },
1268 ),
1269 ]
1270 "###);
1271 }
1272
1273 #[test]
1274 fn test_hover_test_mod_has_action() {
1275 let (_, actions) = check_hover_result(
1276 "
1277 //- /lib.rs
1278 mod tests<|> {
1279 #[test]
1280 fn foo_test() {}
1281 }
1282 ",
1283 &["mod tests"],
1284 );
1285 assert_debug_snapshot!(actions,
1286 @r###"
1287 [
1288 Runnable(
1289 Runnable {
1290 nav: NavigationTarget {
1291 file_id: FileId(
1292 1,
1293 ),
1294 full_range: 0..46,
1295 name: "tests",
1296 kind: MODULE,
1297 focus_range: Some(
1298 4..9,
1299 ),
1300 container_name: None,
1301 description: None,
1302 docs: None,
1303 },
1304 kind: TestMod {
1305 path: "tests",
1306 },
1307 cfg_exprs: [],
1308 },
1309 ),
1310 ]
1311 "###);
1312 }
954} 1313}