From 3ec25841488f9d4325ec25d737c488c18419787c Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Tue, 26 Feb 2019 18:55:08 +0200 Subject: Add support for showing fn signature when hovering --- crates/ra_ide_api/src/hover.rs | 46 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 364bf9f74..729f435d3 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -1,6 +1,6 @@ use ra_db::SourceDatabase; use ra_syntax::{ - AstNode, SyntaxNode, TreeArc, ast, + AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner, TypeParamsOwner}, algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}}, }; @@ -118,9 +118,49 @@ impl NavigationTarget { // TODO: After type inference is done, add type information to improve the output let node = self.node(db)?; + // FIXME: This is copied from `structure.rs` and should probably + // be moved somewhere common + fn collapse_ws(node: &SyntaxNode, output: &mut String) { + let mut can_insert_ws = false; + for line in node.text().chunks().flat_map(|chunk| chunk.lines()) { + let line = line.trim(); + if line.is_empty() { + if can_insert_ws { + output.push_str(" "); + can_insert_ws = false; + } + } else { + output.push_str(line); + can_insert_ws = true; + } + } + } + + fn visit_fn(node: &ast::FnDef) -> Option { + let mut detail = + node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); + + detail.push_str("fn "); + + node.name()?.syntax().text().push_to(&mut detail); + + if let Some(type_param_list) = node.type_param_list() { + collapse_ws(type_param_list.syntax(), &mut detail); + } + if let Some(param_list) = node.param_list() { + collapse_ws(param_list.syntax(), &mut detail); + } + if let Some(ret_type) = node.ret_type() { + detail.push_str(" "); + collapse_ws(ret_type.syntax(), &mut detail); + } + + Some(detail) + } + fn visit_node(node: &T, label: &str) -> Option where - T: ast::NameOwner + ast::VisibilityOwner, + T: NameOwner + VisibilityOwner, { let mut string = node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); @@ -130,7 +170,7 @@ impl NavigationTarget { } visitor() - .visit(|node: &ast::FnDef| visit_node(node, "fn ")) + .visit(visit_fn) .visit(|node: &ast::StructDef| visit_node(node, "struct ")) .visit(|node: &ast::EnumDef| visit_node(node, "enum ")) .visit(|node: &ast::TraitDef| visit_node(node, "trait ")) -- cgit v1.2.3 From 6f5fd6c9de07a2bcc315acae59845ffb79990bc5 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Tue, 26 Feb 2019 18:56:04 +0200 Subject: Add new type HoverResult to contain the results of hovering This makes testing hovers easier as well as allows us to do more things with the results if needed. --- crates/ra_ide_api/src/hover.rs | 160 ++++++++++++++++++++++--- crates/ra_ide_api/src/lib.rs | 3 +- crates/ra_lsp_server/src/main_loop/handlers.rs | 2 +- 3 files changed, 149 insertions(+), 16 deletions(-) (limited to 'crates') 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::{ use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; -pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { +/// Contains the results when hovering over an item +#[derive(Debug, Clone)] +pub struct HoverResult { + results: Vec, + exact: bool, +} + +impl HoverResult { + pub fn new() -> HoverResult { + HoverResult { + results: Vec::new(), + // We assume exact by default + exact: true, + } + } + + pub fn extend(&mut self, item: Option) { + self.results.extend(item); + } + + pub fn is_exact(&self) -> bool { + self.exact + } + + pub fn is_empty(&self) -> bool { + self.results.is_empty() + } + + pub fn len(&self) -> usize { + self.results.len() + } + + pub fn first(&self) -> Option<&str> { + self.results.first().map(String::as_str) + } + + pub fn results(&self) -> &[String] { + &self.results + } + + /// Returns the results converted into markup + /// for displaying in a UI + pub fn to_markup(&self) -> String { + let mut markup = if !self.exact { + 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."); + if !self.results.is_empty() { + msg.push_str(" \nThese items were found instead:"); + } + msg.push_str("\n\n---\n"); + msg + } else { + String::new() + }; + + markup.push_str(&self.results.join("\n\n---\n")); + + markup + } +} + +pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { let file = db.parse(position.file_id); - let mut res = Vec::new(); + let mut res = HoverResult::new(); let mut range = None; if let Some(name_ref) = find_node_at_offset::(file.syntax(), position.offset) { @@ -17,11 +77,9 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option res.extend(doc_text_for(db, nav)), Approximate(navs) => { - 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."); - if !navs.is_empty() { - msg.push_str(" \nThese items were found instead:"); - } - res.push(msg); + // We are no longer exact + res.exact = false; + for nav in navs { res.extend(doc_text_for(db, nav)) } @@ -31,6 +89,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Option Option { let mut detail = node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); @@ -185,7 +245,24 @@ impl NavigationTarget { #[cfg(test)] mod tests { use ra_syntax::TextRange; - use crate::mock_analysis::{single_file_with_position, single_file_with_range}; + use crate::mock_analysis::{single_file_with_position, single_file_with_range, analysis_and_position}; + + fn trim_markup(s: &str) -> &str { + s.trim_start_matches("```rust\n").trim_end_matches("\n```") + } + + fn check_hover_result(fixture: &str, expected: &[&str]) { + let (analysis, position) = analysis_and_position(fixture); + let hover = analysis.hover(position).unwrap().unwrap(); + + for (markup, expected) in + hover.info.results().iter().zip(expected.iter().chain(std::iter::repeat(&""))) + { + assert_eq!(trim_markup(&markup), *expected); + } + + assert_eq!(hover.info.len(), expected.len()); + } #[test] fn hover_shows_type_of_an_expression() { @@ -200,7 +277,62 @@ mod tests { ); let hover = analysis.hover(position).unwrap().unwrap(); assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); - assert_eq!(hover.info, "u32"); + assert_eq!(hover.info.first(), Some("u32")); + } + + #[test] + fn hover_shows_fn_signature() { + // Single file with result + check_hover_result( + r#" + //- /main.rs + pub fn foo() -> u32 { 1 } + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo() -> u32"], + ); + + // Multiple results + check_hover_result( + r#" + //- /a.rs + pub fn foo() -> u32 { 1 } + + //- /b.rs + pub fn foo() -> &str { "" } + + //- /c.rs + pub fn foo(a: u32, b: u32) {} + + //- /main.rs + mod a; + mod b; + mod c; + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], + ); + } + + #[test] + fn hover_shows_fn_signature_with_type_params() { + check_hover_result( + r#" + //- /main.rs + pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str"], + ); } #[test] @@ -217,21 +349,21 @@ mod tests { ); let hover = analysis.hover(position).unwrap().unwrap(); // not the nicest way to show it currently - assert_eq!(hover.info, "Some(T) -> Option"); + assert_eq!(hover.info.first(), Some("Some(T) -> Option")); } #[test] fn hover_for_local_variable() { let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.info, "i32"); + assert_eq!(hover.info.first(), Some("i32")); } #[test] fn hover_for_local_variable_pat() { let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.info, "i32"); + assert_eq!(hover.info.first(), Some("i32")); } #[test] @@ -298,6 +430,6 @@ mod tests { ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.info, "Thing"); + assert_eq!(hover.info.first(), Some("Thing")); } } 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::{ navigation_target::NavigationTarget, references::ReferenceSearchResult, assists::{Assist, AssistId}, + hover::{HoverResult}, }; pub use ra_ide_api_light::{ Fold, FoldKind, HighlightedRange, Severity, StructureNode, LocalEdit, @@ -328,7 +329,7 @@ impl Analysis { } /// Returns a short text describing element at position. - pub fn hover(&self, position: FilePosition) -> Cancelable>> { + pub fn hover(&self, position: FilePosition) -> Cancelable>> { self.with_db(|db| hover::hover(db, position)) } 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( let res = Hover { contents: HoverContents::Markup(MarkupContent { kind: MarkupKind::Markdown, - value: info.info, + value: info.info.to_markup(), }), range: Some(range), }; -- cgit v1.2.3 From 79114c67b10698d6b73b84bdd782d7407752d3ca Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Tue, 26 Feb 2019 21:30:46 +0200 Subject: Replace visit_fn with crate::completion::function_label --- crates/ra_ide_api/src/hover.rs | 45 ++---------------------------------------- 1 file changed, 2 insertions(+), 43 deletions(-) (limited to 'crates') diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 91d8e2ffa..bd3b1e396 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -1,6 +1,6 @@ use ra_db::SourceDatabase; use ra_syntax::{ - AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner, TypeParamsOwner}, + AstNode, SyntaxNode, TreeArc, ast::{self, NameOwner, VisibilityOwner}, algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}}, }; @@ -177,47 +177,6 @@ impl NavigationTarget { // TODO: After type inference is done, add type information to improve the output let node = self.node(db)?; - // FIXME: This is copied from `structure.rs` and should probably - // be moved somewhere common - fn collapse_ws(node: &SyntaxNode, output: &mut String) { - let mut can_insert_ws = false; - for line in node.text().chunks().flat_map(|chunk| chunk.lines()) { - let line = line.trim(); - if line.is_empty() { - if can_insert_ws { - output.push_str(" "); - can_insert_ws = false; - } - } else { - output.push_str(line); - can_insert_ws = true; - } - } - } - - // FIXME: This is also partially copied from `structure.rs` - fn visit_fn(node: &ast::FnDef) -> Option { - let mut detail = - node.visibility().map(|v| format!("{} ", v.syntax().text())).unwrap_or_default(); - - detail.push_str("fn "); - - node.name()?.syntax().text().push_to(&mut detail); - - if let Some(type_param_list) = node.type_param_list() { - collapse_ws(type_param_list.syntax(), &mut detail); - } - if let Some(param_list) = node.param_list() { - collapse_ws(param_list.syntax(), &mut detail); - } - if let Some(ret_type) = node.ret_type() { - detail.push_str(" "); - collapse_ws(ret_type.syntax(), &mut detail); - } - - Some(detail) - } - fn visit_node(node: &T, label: &str) -> Option where T: NameOwner + VisibilityOwner, @@ -230,7 +189,7 @@ impl NavigationTarget { } visitor() - .visit(visit_fn) + .visit(crate::completion::function_label) .visit(|node: &ast::StructDef| visit_node(node, "struct ")) .visit(|node: &ast::EnumDef| visit_node(node, "enum ")) .visit(|node: &ast::TraitDef| visit_node(node, "trait ")) -- cgit v1.2.3 From e3525527e35219e38e811b572406e14119853716 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Wed, 27 Feb 2019 09:49:22 +0200 Subject: Add support for hovering over the name of an item --- crates/ra_ide_api/src/goto_definition.rs | 2 +- crates/ra_ide_api/src/hover.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'crates') 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( Approximate(navs) } -fn name_definition( +pub(crate) fn name_definition( db: &RootDatabase, file_id: FileId, name: &ast::Name, diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index bd3b1e396..ef3b5df29 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -88,6 +88,18 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option(file.syntax(), position.offset) { + let navs = crate::goto_definition::name_definition(db, position.file_id, name); + + if let Some(navs) = navs { + for nav in navs { + res.extend(doc_text_for(db, nav)) + } + } + + if !res.is_empty() && range.is_none() { + range = Some(name.syntax().range()); + } } if range.is_none() { @@ -97,7 +109,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option(a: u32, b: u32) -> u32 {} + + fn main() { + } + "#, + &["pub fn foo(a: u32, b: u32) -> u32"], + ); + } + #[test] fn hover_some() { let (analysis, position) = single_file_with_position( -- cgit v1.2.3