diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 284 |
1 files changed, 271 insertions, 13 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 9636cd0d6..ad78b7671 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -13,14 +13,52 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | 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}, |
17 | FilePosition, RangeInfo, | 17 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | ||
18 | }; | 19 | }; |
20 | use test_utils::mark; | ||
21 | |||
22 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
23 | pub struct HoverConfig { | ||
24 | pub implementations: bool, | ||
25 | pub run: bool, | ||
26 | pub debug: bool, | ||
27 | } | ||
28 | |||
29 | impl Default for HoverConfig { | ||
30 | fn default() -> Self { | ||
31 | Self { implementations: true, run: true, debug: true } | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl 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)] | ||
52 | pub enum HoverAction { | ||
53 | Runnable(Runnable), | ||
54 | Implementaion(FilePosition), | ||
55 | } | ||
19 | 56 | ||
20 | /// Contains the results when hovering over an item | 57 | /// Contains the results when hovering over an item |
21 | #[derive(Debug, Default)] | 58 | #[derive(Debug, Default)] |
22 | pub struct HoverResult { | 59 | pub struct HoverResult { |
23 | results: Vec<String>, | 60 | results: Vec<String>, |
61 | actions: Vec<HoverAction>, | ||
24 | } | 62 | } |
25 | 63 | ||
26 | impl HoverResult { | 64 | impl HoverResult { |
@@ -48,10 +86,20 @@ impl HoverResult { | |||
48 | &self.results | 86 | &self.results |
49 | } | 87 | } |
50 | 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 | |||
51 | /// Returns the results converted into markup | 97 | /// Returns the results converted into markup |
52 | /// for displaying in a UI | 98 | /// for displaying in a UI |
99 | /// | ||
100 | /// Does not process actions! | ||
53 | pub fn to_markup(&self) -> String { | 101 | pub fn to_markup(&self) -> String { |
54 | self.results.join("\n\n---\n") | 102 | self.results.join("\n\n___\n") |
55 | } | 103 | } |
56 | } | 104 | } |
57 | 105 | ||
@@ -82,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
82 | res.extend(hover_text_from_name_kind(db, name_kind)); | 130 | res.extend(hover_text_from_name_kind(db, name_kind)); |
83 | 131 | ||
84 | 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 | |||
85 | return Some(RangeInfo::new(range, res)); | 141 | return Some(RangeInfo::new(range, res)); |
86 | } | 142 | } |
87 | } | 143 | } |
@@ -112,6 +168,56 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
112 | Some(RangeInfo::new(range, res)) | 168 | Some(RangeInfo::new(range, res)) |
113 | } | 169 | } |
114 | 170 | ||
171 | fn 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 | |||
191 | fn 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 | |||
115 | fn hover_text( | 221 | fn hover_text( |
116 | docs: Option<String>, | 222 | docs: Option<String>, |
117 | desc: Option<String>, | 223 | desc: Option<String>, |
@@ -228,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
228 | 334 | ||
229 | #[cfg(test)] | 335 | #[cfg(test)] |
230 | mod tests { | 336 | mod tests { |
337 | use super::*; | ||
338 | use insta::assert_debug_snapshot; | ||
339 | |||
231 | use ra_db::FileLoader; | 340 | use ra_db::FileLoader; |
232 | use ra_syntax::TextRange; | 341 | use ra_syntax::TextRange; |
233 | 342 | ||
@@ -241,7 +350,15 @@ mod tests { | |||
241 | s.map(trim_markup) | 350 | s.map(trim_markup) |
242 | } | 351 | } |
243 | 352 | ||
244 | 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>) { | ||
245 | let (analysis, position) = analysis_and_position(fixture); | 362 | let (analysis, position) = analysis_and_position(fixture); |
246 | let hover = analysis.hover(position).unwrap().unwrap(); | 363 | let hover = analysis.hover(position).unwrap().unwrap(); |
247 | let mut results = Vec::from(hover.info.results()); | 364 | let mut results = Vec::from(hover.info.results()); |
@@ -256,7 +373,7 @@ mod tests { | |||
256 | assert_eq!(hover.info.len(), expected.len()); | 373 | assert_eq!(hover.info.len(), expected.len()); |
257 | 374 | ||
258 | let content = analysis.db.file_text(position.file_id); | 375 | let content = analysis.db.file_text(position.file_id); |
259 | content[hover.range].to_string() | 376 | (content[hover.range].to_string(), hover.info.actions().to_vec()) |
260 | } | 377 | } |
261 | 378 | ||
262 | fn check_hover_no_result(fixture: &str) { | 379 | fn check_hover_no_result(fixture: &str) { |
@@ -457,7 +574,7 @@ struct Test<K, T = u8> { | |||
457 | } | 574 | } |
458 | 575 | ||
459 | fn main() { | 576 | fn main() { |
460 | let zz<|> = Test { t: 23, k: 33 }; | 577 | let zz<|> = Test { t: 23u8, k: 33 }; |
461 | }"#, | 578 | }"#, |
462 | &["Test<i32, u8>"], | 579 | &["Test<i32, u8>"], |
463 | ); | 580 | ); |
@@ -746,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
746 | 863 | ||
747 | #[test] | 864 | #[test] |
748 | fn test_hover_through_macro() { | 865 | fn test_hover_through_macro() { |
749 | let hover_on = check_hover_result( | 866 | let (hover_on, _) = check_hover_result( |
750 | " | 867 | " |
751 | //- /lib.rs | 868 | //- /lib.rs |
752 | macro_rules! id { | 869 | macro_rules! id { |
@@ -767,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
767 | 884 | ||
768 | #[test] | 885 | #[test] |
769 | fn test_hover_through_expr_in_macro() { | 886 | fn test_hover_through_expr_in_macro() { |
770 | let hover_on = check_hover_result( | 887 | let (hover_on, _) = check_hover_result( |
771 | " | 888 | " |
772 | //- /lib.rs | 889 | //- /lib.rs |
773 | macro_rules! id { | 890 | macro_rules! id { |
@@ -785,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
785 | 902 | ||
786 | #[test] | 903 | #[test] |
787 | fn test_hover_through_expr_in_macro_recursive() { | 904 | fn test_hover_through_expr_in_macro_recursive() { |
788 | let hover_on = check_hover_result( | 905 | let (hover_on, _) = check_hover_result( |
789 | " | 906 | " |
790 | //- /lib.rs | 907 | //- /lib.rs |
791 | macro_rules! id_deep { | 908 | macro_rules! id_deep { |
@@ -806,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
806 | 923 | ||
807 | #[test] | 924 | #[test] |
808 | fn test_hover_through_func_in_macro_recursive() { | 925 | fn test_hover_through_func_in_macro_recursive() { |
809 | let hover_on = check_hover_result( | 926 | let (hover_on, _) = check_hover_result( |
810 | " | 927 | " |
811 | //- /lib.rs | 928 | //- /lib.rs |
812 | macro_rules! id_deep { | 929 | macro_rules! id_deep { |
@@ -830,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
830 | 947 | ||
831 | #[test] | 948 | #[test] |
832 | fn test_hover_through_literal_string_in_macro() { | 949 | fn test_hover_through_literal_string_in_macro() { |
833 | let hover_on = check_hover_result( | 950 | let (hover_on, _) = check_hover_result( |
834 | r#" | 951 | r#" |
835 | //- /lib.rs | 952 | //- /lib.rs |
836 | macro_rules! arr { | 953 | macro_rules! arr { |
@@ -849,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
849 | 966 | ||
850 | #[test] | 967 | #[test] |
851 | fn test_hover_through_assert_macro() { | 968 | fn test_hover_through_assert_macro() { |
852 | let hover_on = check_hover_result( | 969 | let (hover_on, _) = check_hover_result( |
853 | r#" | 970 | r#" |
854 | //- /lib.rs | 971 | //- /lib.rs |
855 | #[rustc_builtin_macro] | 972 | #[rustc_builtin_macro] |
@@ -925,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
925 | 1042 | ||
926 | #[test] | 1043 | #[test] |
927 | fn test_hover_trait_show_qualifiers() { | 1044 | fn test_hover_trait_show_qualifiers() { |
928 | check_hover_result( | 1045 | let (_, actions) = check_hover_result( |
929 | " | 1046 | " |
930 | //- /lib.rs | 1047 | //- /lib.rs |
931 | unsafe trait foo<|>() {} | 1048 | unsafe trait foo<|>() {} |
932 | ", | 1049 | ", |
933 | &["unsafe trait foo"], | 1050 | &["unsafe trait foo"], |
934 | ); | 1051 | ); |
1052 | assert_impl_action(&actions[0], 13); | ||
935 | } | 1053 | } |
936 | 1054 | ||
937 | #[test] | 1055 | #[test] |
@@ -1003,6 +1121,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1003 | 1121 | ||
1004 | #[test] | 1122 | #[test] |
1005 | fn test_hover_macro_generated_struct_fn_doc_comment() { | 1123 | fn test_hover_macro_generated_struct_fn_doc_comment() { |
1124 | mark::check!(hover_macro_generated_struct_fn_doc_comment); | ||
1125 | |||
1006 | check_hover_result( | 1126 | check_hover_result( |
1007 | r#" | 1127 | r#" |
1008 | //- /lib.rs | 1128 | //- /lib.rs |
@@ -1029,6 +1149,8 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1029 | 1149 | ||
1030 | #[test] | 1150 | #[test] |
1031 | fn test_hover_macro_generated_struct_fn_doc_attr() { | 1151 | fn test_hover_macro_generated_struct_fn_doc_attr() { |
1152 | mark::check!(hover_macro_generated_struct_fn_doc_attr); | ||
1153 | |||
1032 | check_hover_result( | 1154 | check_hover_result( |
1033 | r#" | 1155 | r#" |
1034 | //- /lib.rs | 1156 | //- /lib.rs |
@@ -1052,4 +1174,140 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1052 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], | 1174 | &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], |
1053 | ); | 1175 | ); |
1054 | } | 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 | } | ||
1055 | } | 1313 | } |