aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-27 11:50:38 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-27 11:50:38 +0000
commit1927eb088ac9aa3851f77bb929296873ccb4faed (patch)
treeaf3c0a5ea5a11a2998d12f61044be4c3e150d1e9 /crates
parentc8e78809e5a300f3c2770f50b6d8b1c4bff11531 (diff)
parente3525527e35219e38e811b572406e14119853716 (diff)
Merge #901
901: Add basic support for showing fn signature when hovering r=matklad a=vipentti This adds basic support for displaying function signature when hovering over a usage of a function. Additionally refactored `hover` to return `HoverResult` to ease with testing and in general to be more robust. Co-authored-by: Ville Penttinen <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs2
-rw-r--r--crates/ra_ide_api/src/hover.rs193
-rw-r--r--crates/ra_ide_api/src/lib.rs3
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs2
4 files changed, 179 insertions, 21 deletions
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs
index 1833e57d5..da33739be 100644
--- a/crates/ra_ide_api/src/goto_definition.rs
+++ b/crates/ra_ide_api/src/goto_definition.rs
@@ -110,7 +110,7 @@ pub(crate) fn reference_definition(
110 Approximate(navs) 110 Approximate(navs)
111} 111}
112 112
113fn name_definition( 113pub(crate) fn name_definition(
114 db: &RootDatabase, 114 db: &RootDatabase,
115 file_id: FileId, 115 file_id: FileId,
116 name: &ast::Name, 116 name: &ast::Name,
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs
index 364bf9f74..ef3b5df29 100644
--- a/crates/ra_ide_api/src/hover.rs
+++ b/crates/ra_ide_api/src/hover.rs
@@ -1,14 +1,74 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::{ 2use ra_syntax::{
3 AstNode, SyntaxNode, TreeArc, ast, 3 AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner},
4 algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}}, 4 algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}},
5}; 5};
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 }
@@ -30,7 +88,20 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
30 if !res.is_empty() { 88 if !res.is_empty() {
31 range = Some(name_ref.syntax().range()) 89 range = Some(name_ref.syntax().range())
32 } 90 }
91 } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
92 let navs = crate::goto_definition::name_definition(db, position.file_id, name);
93
94 if let Some(navs) = navs {
95 for nav in navs {
96 res.extend(doc_text_for(db, nav))
97 }
98 }
99
100 if !res.is_empty() && range.is_none() {
101 range = Some(name.syntax().range());
102 }
33 } 103 }
104
34 if range.is_none() { 105 if range.is_none() {
35 let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| { 106 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()) 107 leaf.ancestors().find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())
@@ -38,13 +109,13 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
38 let frange = FileRange { file_id: position.file_id, range: node.range() }; 109 let frange = FileRange { file_id: position.file_id, range: node.range() };
39 res.extend(type_of(db, frange).map(Into::into)); 110 res.extend(type_of(db, frange).map(Into::into));
40 range = Some(node.range()); 111 range = Some(node.range());
41 }; 112 }
42 113
43 let range = range?; 114 let range = range?;
44 if res.is_empty() { 115 if res.is_empty() {
45 return None; 116 return None;
46 } 117 }
47 let res = RangeInfo::new(range, res.join("\n\n---\n")); 118 let res = RangeInfo::new(range, res);
48 Some(res) 119 Some(res)
49} 120}
50 121
@@ -120,7 +191,7 @@ impl NavigationTarget {
120 191
121 fn visit_node<T>(node: &T, label: &str) -> Option<String> 192 fn visit_node<T>(node: &T, label: &str) -> Option<String>
122 where 193 where
123 T: ast::NameOwner + ast::VisibilityOwner, 194 T: NameOwner + VisibilityOwner,
124 { 195 {
125 let mut string = 196 let mut string =
126 node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); 197 node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default();
@@ -130,7 +201,7 @@ impl NavigationTarget {
130 } 201 }
131 202
132 visitor() 203 visitor()
133 .visit(|node: &ast::FnDef| visit_node(node, "fn ")) 204 .visit(crate::completion::function_label)
134 .visit(|node: &ast::StructDef| visit_node(node, "struct ")) 205 .visit(|node: &ast::StructDef| visit_node(node, "struct "))
135 .visit(|node: &ast::EnumDef| visit_node(node, "enum ")) 206 .visit(|node: &ast::EnumDef| visit_node(node, "enum "))
136 .visit(|node: &ast::TraitDef| visit_node(node, "trait ")) 207 .visit(|node: &ast::TraitDef| visit_node(node, "trait "))
@@ -145,7 +216,24 @@ impl NavigationTarget {
145#[cfg(test)] 216#[cfg(test)]
146mod tests { 217mod tests {
147 use ra_syntax::TextRange; 218 use ra_syntax::TextRange;
148 use crate::mock_analysis::{single_file_with_position, single_file_with_range}; 219 use crate::mock_analysis::{single_file_with_position, single_file_with_range, analysis_and_position};
220
221 fn trim_markup(s: &str) -> &str {
222 s.trim_start_matches("```rust\n").trim_end_matches("\n```")
223 }
224
225 fn check_hover_result(fixture: &str, expected: &[&str]) {
226 let (analysis, position) = analysis_and_position(fixture);
227 let hover = analysis.hover(position).unwrap().unwrap();
228
229 for (markup, expected) in
230 hover.info.results().iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
231 {
232 assert_eq!(trim_markup(&markup), *expected);
233 }
234
235 assert_eq!(hover.info.len(), expected.len());
236 }
149 237
150 #[test] 238 #[test]
151 fn hover_shows_type_of_an_expression() { 239 fn hover_shows_type_of_an_expression() {
@@ -160,7 +248,76 @@ mod tests {
160 ); 248 );
161 let hover = analysis.hover(position).unwrap().unwrap(); 249 let hover = analysis.hover(position).unwrap().unwrap();
162 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); 250 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
163 assert_eq!(hover.info, "u32"); 251 assert_eq!(hover.info.first(), Some("u32"));
252 }
253
254 #[test]
255 fn hover_shows_fn_signature() {
256 // Single file with result
257 check_hover_result(
258 r#"
259 //- /main.rs
260 pub fn foo() -> u32 { 1 }
261
262 fn main() {
263 let foo_test = fo<|>o();
264 }
265 "#,
266 &["pub fn foo() -> u32"],
267 );
268
269 // Multiple results
270 check_hover_result(
271 r#"
272 //- /a.rs
273 pub fn foo() -> u32 { 1 }
274
275 //- /b.rs
276 pub fn foo() -> &str { "" }
277
278 //- /c.rs
279 pub fn foo(a: u32, b: u32) {}
280
281 //- /main.rs
282 mod a;
283 mod b;
284 mod c;
285
286 fn main() {
287 let foo_test = fo<|>o();
288 }
289 "#,
290 &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"],
291 );
292 }
293
294 #[test]
295 fn hover_shows_fn_signature_with_type_params() {
296 check_hover_result(
297 r#"
298 //- /main.rs
299 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
300
301 fn main() {
302 let foo_test = fo<|>o();
303 }
304 "#,
305 &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"],
306 );
307 }
308
309 #[test]
310 fn hover_shows_fn_signature_on_fn_name() {
311 check_hover_result(
312 r#"
313 //- /main.rs
314 pub fn foo<|>(a: u32, b: u32) -> u32 {}
315
316 fn main() {
317 }
318 "#,
319 &["pub fn foo(a: u32, b: u32) -> u32"],
320 );
164 } 321 }
165 322
166 #[test] 323 #[test]
@@ -177,21 +334,21 @@ mod tests {
177 ); 334 );
178 let hover = analysis.hover(position).unwrap().unwrap(); 335 let hover = analysis.hover(position).unwrap().unwrap();
179 // not the nicest way to show it currently 336 // not the nicest way to show it currently
180 assert_eq!(hover.info, "Some<i32>(T) -> Option<T>"); 337 assert_eq!(hover.info.first(), Some("Some<i32>(T) -> Option<T>"));
181 } 338 }
182 339
183 #[test] 340 #[test]
184 fn hover_for_local_variable() { 341 fn hover_for_local_variable() {
185 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); 342 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
186 let hover = analysis.hover(position).unwrap().unwrap(); 343 let hover = analysis.hover(position).unwrap().unwrap();
187 assert_eq!(hover.info, "i32"); 344 assert_eq!(hover.info.first(), Some("i32"));
188 } 345 }
189 346
190 #[test] 347 #[test]
191 fn hover_for_local_variable_pat() { 348 fn hover_for_local_variable_pat() {
192 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); 349 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
193 let hover = analysis.hover(position).unwrap().unwrap(); 350 let hover = analysis.hover(position).unwrap().unwrap();
194 assert_eq!(hover.info, "i32"); 351 assert_eq!(hover.info.first(), Some("i32"));
195 } 352 }
196 353
197 #[test] 354 #[test]
@@ -258,6 +415,6 @@ mod tests {
258 ", 415 ",
259 ); 416 );
260 let hover = analysis.hover(position).unwrap().unwrap(); 417 let hover = analysis.hover(position).unwrap().unwrap();
261 assert_eq!(hover.info, "Thing"); 418 assert_eq!(hover.info.first(), Some("Thing"));
262 } 419 }
263} 420}
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
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 5da731801..dce6fcc67 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -441,7 +441,7 @@ pub fn handle_hover(
441 let res = Hover { 441 let res = Hover {
442 contents: HoverContents::Markup(MarkupContent { 442 contents: HoverContents::Markup(MarkupContent {
443 kind: MarkupKind::Markdown, 443 kind: MarkupKind::Markdown,
444 value: info.info, 444 value: info.info.to_markup(),
445 }), 445 }),
446 range: Some(range), 446 range: Some(range),
447 }; 447 };