diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-27 11:50:38 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-02-27 11:50:38 +0000 |
commit | 1927eb088ac9aa3851f77bb929296873ccb4faed (patch) | |
tree | af3c0a5ea5a11a2998d12f61044be4c3e150d1e9 | |
parent | c8e78809e5a300f3c2770f50b6d8b1c4bff11531 (diff) | |
parent | e3525527e35219e38e811b572406e14119853716 (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]>
-rw-r--r-- | crates/ra_ide_api/src/goto_definition.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 193 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 2 |
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 | ||
113 | fn name_definition( | 113 | pub(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 @@ | |||
1 | use ra_db::SourceDatabase; | 1 | use ra_db::SourceDatabase; |
2 | use ra_syntax::{ | 2 | use 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 | ||
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 | } |
@@ -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)] |
146 | mod tests { | 217 | mod 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 | }; |
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 | ||
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 | }; |