From 5b573deb20b15451788dd2861e9fc6e69ed0472e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 8 Jan 2019 22:33:36 +0300 Subject: fix usages after rename --- crates/ra_ide_api/src/hover.rs | 257 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 crates/ra_ide_api/src/hover.rs (limited to 'crates/ra_ide_api/src/hover.rs') diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs new file mode 100644 index 000000000..475524ee1 --- /dev/null +++ b/crates/ra_ide_api/src/hover.rs @@ -0,0 +1,257 @@ +use ra_db::{Cancelable, SyntaxDatabase}; +use ra_syntax::{ + AstNode, SyntaxNode, TreePtr, + ast::{self, NameOwner}, + algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}}, +}; + +use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; + +pub(crate) fn hover( + db: &RootDatabase, + position: FilePosition, +) -> Cancelable>> { + let file = db.source_file(position.file_id); + let mut res = Vec::new(); + + let mut range = None; + if let Some(name_ref) = find_node_at_offset::(file.syntax(), position.offset) { + let navs = crate::goto_defenition::reference_defenition(db, position.file_id, name_ref)?; + for nav in navs { + res.extend(doc_text_for(db, nav)?) + } + if !res.is_empty() { + range = Some(name_ref.syntax().range()) + } + } + if range.is_none() { + let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| { + leaf.ancestors() + .find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some()) + }); + let node = ctry!(node); + let frange = FileRange { + file_id: position.file_id, + range: node.range(), + }; + res.extend(type_of(db, frange)?); + range = Some(node.range()); + }; + + let range = ctry!(range); + if res.is_empty() { + return Ok(None); + } + let res = RangeInfo::new(range, res.join("\n\n---\n")); + Ok(Some(res)) +} + +pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Cancelable> { + let file = db.source_file(frange.file_id); + let syntax = file.syntax(); + let leaf_node = find_covering_node(syntax, frange.range); + // if we picked identifier, expand to pattern/expression + let node = leaf_node + .ancestors() + .take_while(|it| it.range() == leaf_node.range()) + .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some()) + .unwrap_or(leaf_node); + let parent_fn = ctry!(node.ancestors().find_map(ast::FnDef::cast)); + let function = ctry!(hir::source_binder::function_from_source( + db, + frange.file_id, + parent_fn + )?); + let infer = function.infer(db)?; + let syntax_mapping = function.body_syntax_mapping(db)?; + if let Some(expr) = ast::Expr::cast(node).and_then(|e| syntax_mapping.node_expr(e)) { + Ok(Some(infer[expr].to_string())) + } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| syntax_mapping.node_pat(p)) { + Ok(Some(infer[pat].to_string())) + } else { + Ok(None) + } +} + +// FIXME: this should not really use navigation target. Rather, approximatelly +// resovled symbol should return a `DefId`. +fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Cancelable> { + let result = match (nav.description(db), nav.docs(db)) { + (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs), + (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"), + (None, Some(docs)) => Some(docs), + _ => None, + }; + + Ok(result) +} + +impl NavigationTarget { + fn node(&self, db: &RootDatabase) -> Option> { + let source_file = db.source_file(self.file_id); + let source_file = source_file.syntax(); + let node = source_file + .descendants() + .find(|node| node.kind() == self.kind && node.range() == self.range)? + .to_owned(); + Some(node) + } + + fn docs(&self, db: &RootDatabase) -> Option { + let node = self.node(db)?; + fn doc_comments(node: &N) -> Option { + let comments = node.doc_comment_text(); + if comments.is_empty() { + None + } else { + Some(comments) + } + } + + visitor() + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .visit(doc_comments::) + .accept(&node)? + } + + /// Get a description of this node. + /// + /// e.g. `struct Name`, `enum Name`, `fn Name` + fn description(&self, db: &RootDatabase) -> Option { + // TODO: After type inference is done, add type information to improve the output + let node = self.node(db)?; + // TODO: Refactor to be have less repetition + visitor() + .visit(|node: &ast::FnDef| { + let mut string = "fn ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::StructDef| { + let mut string = "struct ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::EnumDef| { + let mut string = "enum ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::TraitDef| { + let mut string = "trait ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::Module| { + let mut string = "mod ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::TypeDef| { + let mut string = "type ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::ConstDef| { + let mut string = "const ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .visit(|node: &ast::StaticDef| { + let mut string = "static ".to_string(); + node.name()?.syntax().text().push_to(&mut string); + Some(string) + }) + .accept(&node)? + } +} + +#[cfg(test)] +mod tests { + use ra_syntax::TextRange; + use crate::mock_analysis::{single_file_with_position, single_file_with_range}; + + #[test] + fn hover_shows_type_of_an_expression() { + let (analysis, position) = single_file_with_position( + " + pub fn foo() -> u32 { 1 } + + fn main() { + let foo_test = foo()<|>; + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); + assert_eq!(hover.info, "u32"); + } + + #[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"); + } + + #[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"); + } + + #[test] + fn test_type_of_for_function() { + let (analysis, range) = single_file_with_range( + " + pub fn foo() -> u32 { 1 }; + + fn main() { + let foo_test = <|>foo()<|>; + } + ", + ); + + let type_name = analysis.type_of(range).unwrap().unwrap(); + assert_eq!("u32", &type_name); + } + + // FIXME: improve type_of to make this work + #[test] + fn test_type_of_for_expr_1() { + let (analysis, range) = single_file_with_range( + " + fn main() { + let foo = <|>1 + foo_test<|>; + } + ", + ); + + let type_name = analysis.type_of(range).unwrap().unwrap(); + assert_eq!("[unknown]", &type_name); + } + + // FIXME: improve type_of to make this work + #[test] + fn test_type_of_for_expr_2() { + let (analysis, range) = single_file_with_range( + " + fn main() { + let foo: usize = 1; + let bar = <|>1 + foo_test<|>; + } + ", + ); + + let type_name = analysis.type_of(range).unwrap().unwrap(); + assert_eq!("[unknown]", &type_name); + } + +} -- cgit v1.2.3