aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r--crates/ra_ide_api/src/hover.rs160
-rw-r--r--crates/ra_ide_api/src/lib.rs3
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
7use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; 7use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget};
8 8
9pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<String>> { 9/// Contains the results when hovering over an item
10#[derive(Debug, Clone)]
11pub struct HoverResult {
12 results: Vec<String>,
13 exact: bool,
14}
15
16impl 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
69pub(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)]
186mod tests { 246mod 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};
62pub use ra_ide_api_light::{ 63pub 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