diff options
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 160 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 3 |
2 files changed, 148 insertions, 15 deletions
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 729f435d3..91d8e2ffa 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs | |||
@@ -6,9 +6,69 @@ use ra_syntax::{ | |||
6 | 6 | ||
7 | use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; | 7 | use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; |
8 | 8 | ||
9 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<String>> { | 9 | /// Contains the results when hovering over an item |
10 | #[derive(Debug, Clone)] | ||
11 | pub struct HoverResult { | ||
12 | results: Vec<String>, | ||
13 | exact: bool, | ||
14 | } | ||
15 | |||
16 | impl HoverResult { | ||
17 | pub fn new() -> HoverResult { | ||
18 | HoverResult { | ||
19 | results: Vec::new(), | ||
20 | // We assume exact by default | ||
21 | exact: true, | ||
22 | } | ||
23 | } | ||
24 | |||
25 | pub fn extend(&mut self, item: Option<String>) { | ||
26 | self.results.extend(item); | ||
27 | } | ||
28 | |||
29 | pub fn is_exact(&self) -> bool { | ||
30 | self.exact | ||
31 | } | ||
32 | |||
33 | pub fn is_empty(&self) -> bool { | ||
34 | self.results.is_empty() | ||
35 | } | ||
36 | |||
37 | pub fn len(&self) -> usize { | ||
38 | self.results.len() | ||
39 | } | ||
40 | |||
41 | pub fn first(&self) -> Option<&str> { | ||
42 | self.results.first().map(String::as_str) | ||
43 | } | ||
44 | |||
45 | pub fn results(&self) -> &[String] { | ||
46 | &self.results | ||
47 | } | ||
48 | |||
49 | /// Returns the results converted into markup | ||
50 | /// for displaying in a UI | ||
51 | pub fn to_markup(&self) -> String { | ||
52 | let mut markup = if !self.exact { | ||
53 | let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support glob imports or traits."); | ||
54 | if !self.results.is_empty() { | ||
55 | msg.push_str(" \nThese items were found instead:"); | ||
56 | } | ||
57 | msg.push_str("\n\n---\n"); | ||
58 | msg | ||
59 | } else { | ||
60 | String::new() | ||
61 | }; | ||
62 | |||
63 | markup.push_str(&self.results.join("\n\n---\n")); | ||
64 | |||
65 | markup | ||
66 | } | ||
67 | } | ||
68 | |||
69 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
10 | let file = db.parse(position.file_id); | 70 | let file = db.parse(position.file_id); |
11 | let mut res = Vec::new(); | 71 | let mut res = HoverResult::new(); |
12 | 72 | ||
13 | let mut range = None; | 73 | let mut range = None; |
14 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { | 74 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) { |
@@ -17,11 +77,9 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
17 | match ref_result { | 77 | match ref_result { |
18 | Exact(nav) => res.extend(doc_text_for(db, nav)), | 78 | Exact(nav) => res.extend(doc_text_for(db, nav)), |
19 | Approximate(navs) => { | 79 | Approximate(navs) => { |
20 | let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support glob imports or traits."); | 80 | // We are no longer exact |
21 | if !navs.is_empty() { | 81 | res.exact = false; |
22 | msg.push_str(" \nThese items were found instead:"); | 82 | |
23 | } | ||
24 | res.push(msg); | ||
25 | for nav in navs { | 83 | for nav in navs { |
26 | res.extend(doc_text_for(db, nav)) | 84 | res.extend(doc_text_for(db, nav)) |
27 | } | 85 | } |
@@ -31,6 +89,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
31 | range = Some(name_ref.syntax().range()) | 89 | range = Some(name_ref.syntax().range()) |
32 | } | 90 | } |
33 | } | 91 | } |
92 | |||
34 | if range.is_none() { | 93 | if range.is_none() { |
35 | let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| { | 94 | let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| { |
36 | leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) | 95 | leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) |
@@ -44,7 +103,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
44 | if res.is_empty() { | 103 | if res.is_empty() { |
45 | return None; | 104 | return None; |
46 | } | 105 | } |
47 | let res = RangeInfo::new(range, res.join("\n\n---\n")); | 106 | let res = RangeInfo::new(range, res); |
48 | Some(res) | 107 | Some(res) |
49 | } | 108 | } |
50 | 109 | ||
@@ -136,6 +195,7 @@ impl NavigationTarget { | |||
136 | } | 195 | } |
137 | } | 196 | } |
138 | 197 | ||
198 | // FIXME: This is also partially copied from `structure.rs` | ||
139 | fn visit_fn(node: &ast::FnDef) -> Option<String> { | 199 | fn visit_fn(node: &ast::FnDef) -> Option<String> { |
140 | let mut detail = | 200 | let mut detail = |
141 | node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); | 201 | node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); |
@@ -185,7 +245,24 @@ impl NavigationTarget { | |||
185 | #[cfg(test)] | 245 | #[cfg(test)] |
186 | mod tests { | 246 | mod tests { |
187 | use ra_syntax::TextRange; | 247 | use ra_syntax::TextRange; |
188 | use crate::mock_analysis::{single_file_with_position, single_file_with_range}; | 248 | use crate::mock_analysis::{single_file_with_position, single_file_with_range, analysis_and_position}; |
249 | |||
250 | fn trim_markup(s: &str) -> &str { | ||
251 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") | ||
252 | } | ||
253 | |||
254 | fn check_hover_result(fixture: &str, expected: &[&str]) { | ||
255 | let (analysis, position) = analysis_and_position(fixture); | ||
256 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
257 | |||
258 | for (markup, expected) in | ||
259 | hover.info.results().iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>"))) | ||
260 | { | ||
261 | assert_eq!(trim_markup(&markup), *expected); | ||
262 | } | ||
263 | |||
264 | assert_eq!(hover.info.len(), expected.len()); | ||
265 | } | ||
189 | 266 | ||
190 | #[test] | 267 | #[test] |
191 | fn hover_shows_type_of_an_expression() { | 268 | fn hover_shows_type_of_an_expression() { |
@@ -200,7 +277,62 @@ mod tests { | |||
200 | ); | 277 | ); |
201 | let hover = analysis.hover(position).unwrap().unwrap(); | 278 | let hover = analysis.hover(position).unwrap().unwrap(); |
202 | assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); | 279 | assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); |
203 | assert_eq!(hover.info, "u32"); | 280 | assert_eq!(hover.info.first(), Some("u32")); |
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn hover_shows_fn_signature() { | ||
285 | // Single file with result | ||
286 | check_hover_result( | ||
287 | r#" | ||
288 | //- /main.rs | ||
289 | pub fn foo() -> u32 { 1 } | ||
290 | |||
291 | fn main() { | ||
292 | let foo_test = fo<|>o(); | ||
293 | } | ||
294 | "#, | ||
295 | &["pub fn foo() -> u32"], | ||
296 | ); | ||
297 | |||
298 | // Multiple results | ||
299 | check_hover_result( | ||
300 | r#" | ||
301 | //- /a.rs | ||
302 | pub fn foo() -> u32 { 1 } | ||
303 | |||
304 | //- /b.rs | ||
305 | pub fn foo() -> &str { "" } | ||
306 | |||
307 | //- /c.rs | ||
308 | pub fn foo(a: u32, b: u32) {} | ||
309 | |||
310 | //- /main.rs | ||
311 | mod a; | ||
312 | mod b; | ||
313 | mod c; | ||
314 | |||
315 | fn main() { | ||
316 | let foo_test = fo<|>o(); | ||
317 | } | ||
318 | "#, | ||
319 | &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], | ||
320 | ); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn hover_shows_fn_signature_with_type_params() { | ||
325 | check_hover_result( | ||
326 | r#" | ||
327 | //- /main.rs | ||
328 | pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { } | ||
329 | |||
330 | fn main() { | ||
331 | let foo_test = fo<|>o(); | ||
332 | } | ||
333 | "#, | ||
334 | &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"], | ||
335 | ); | ||
204 | } | 336 | } |
205 | 337 | ||
206 | #[test] | 338 | #[test] |
@@ -217,21 +349,21 @@ mod tests { | |||
217 | ); | 349 | ); |
218 | let hover = analysis.hover(position).unwrap().unwrap(); | 350 | let hover = analysis.hover(position).unwrap().unwrap(); |
219 | // not the nicest way to show it currently | 351 | // not the nicest way to show it currently |
220 | assert_eq!(hover.info, "Some<i32>(T) -> Option<T>"); | 352 | assert_eq!(hover.info.first(), Some("Some<i32>(T) -> Option<T>")); |
221 | } | 353 | } |
222 | 354 | ||
223 | #[test] | 355 | #[test] |
224 | fn hover_for_local_variable() { | 356 | fn hover_for_local_variable() { |
225 | let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); | 357 | let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); |
226 | let hover = analysis.hover(position).unwrap().unwrap(); | 358 | let hover = analysis.hover(position).unwrap().unwrap(); |
227 | assert_eq!(hover.info, "i32"); | 359 | assert_eq!(hover.info.first(), Some("i32")); |
228 | } | 360 | } |
229 | 361 | ||
230 | #[test] | 362 | #[test] |
231 | fn hover_for_local_variable_pat() { | 363 | fn hover_for_local_variable_pat() { |
232 | let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); | 364 | let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); |
233 | let hover = analysis.hover(position).unwrap().unwrap(); | 365 | let hover = analysis.hover(position).unwrap().unwrap(); |
234 | assert_eq!(hover.info, "i32"); | 366 | assert_eq!(hover.info.first(), Some("i32")); |
235 | } | 367 | } |
236 | 368 | ||
237 | #[test] | 369 | #[test] |
@@ -298,6 +430,6 @@ mod tests { | |||
298 | ", | 430 | ", |
299 | ); | 431 | ); |
300 | let hover = analysis.hover(position).unwrap().unwrap(); | 432 | let hover = analysis.hover(position).unwrap().unwrap(); |
301 | assert_eq!(hover.info, "Thing"); | 433 | assert_eq!(hover.info.first(), Some("Thing")); |
302 | } | 434 | } |
303 | } | 435 | } |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 076a8396c..6546d0644 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -58,6 +58,7 @@ pub use crate::{ | |||
58 | navigation_target::NavigationTarget, | 58 | navigation_target::NavigationTarget, |
59 | references::ReferenceSearchResult, | 59 | references::ReferenceSearchResult, |
60 | assists::{Assist, AssistId}, | 60 | assists::{Assist, AssistId}, |
61 | hover::{HoverResult}, | ||
61 | }; | 62 | }; |
62 | pub use ra_ide_api_light::{ | 63 | pub use ra_ide_api_light::{ |
63 | Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit, | 64 | Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit, |
@@ -328,7 +329,7 @@ impl Analysis { | |||
328 | } | 329 | } |
329 | 330 | ||
330 | /// Returns a short text describing element at position. | 331 | /// Returns a short text describing element at position. |
331 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<String>>> { | 332 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> { |
332 | self.with_db(|db| hover::hover(db, position)) | 333 | self.with_db(|db| hover::hover(db, position)) |
333 | } | 334 | } |
334 | 335 | ||