1 use ra_db::SourceDatabase;
3 AstNode, SyntaxNode, TreeArc, ast,
4 algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, visit::{visitor, Visitor}},
7 use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget};
9 pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<String>> {
10 let file = db.parse(position.file_id);
11 let mut res = Vec::new();
14 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
15 use crate::goto_definition::{ReferenceResult::*, reference_definition};
16 let ref_result = reference_definition(db, position.file_id, name_ref);
18 Exact(nav) => res.extend(doc_text_for(db, nav)),
19 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.");
22 msg.push_str(" \nThese items were found instead:");
26 res.extend(doc_text_for(db, nav))
31 range = Some(name_ref.syntax().range())
35 let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| {
37 .find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())
39 let frange = FileRange {
40 file_id: position.file_id,
43 res.extend(type_of(db, frange).map(Into::into));
44 range = Some(node.range());
51 let res = RangeInfo::new(range, res.join("\n\n---\n"));
55 pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
56 let file = db.parse(frange.file_id);
57 let syntax = file.syntax();
58 let leaf_node = find_covering_node(syntax, frange.range);
59 // if we picked identifier, expand to pattern/expression
62 .take_while(|it| it.range() == leaf_node.range())
63 .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())
64 .unwrap_or(leaf_node);
65 let parent_fn = node.ancestors().find_map(ast::FnDef::cast)?;
66 let function = hir::source_binder::function_from_source(db, frange.file_id, parent_fn)?;
67 let infer = function.infer(db);
68 let syntax_mapping = function.body_syntax_mapping(db);
69 if let Some(expr) = ast::Expr::cast(node).and_then(|e| syntax_mapping.node_expr(e)) {
70 Some(infer[expr].to_string())
71 } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| syntax_mapping.node_pat(p)) {
72 Some(infer[pat].to_string())
78 // FIXME: this should not really use navigation target. Rather, approximatelly
79 // resovled symbol should return a `DefId`.
80 fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Option<String> {
81 match (nav.description(db), nav.docs(db)) {
82 (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs),
83 (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"),
84 (None, Some(docs)) => Some(docs),
89 impl NavigationTarget {
90 fn node(&self, db: &RootDatabase) -> Option<TreeArc<SyntaxNode>> {
91 let source_file = db.parse(self.file_id());
92 let source_file = source_file.syntax();
93 let node = source_file
95 .find(|node| node.kind() == self.kind() && node.range() == self.full_range())?
100 fn docs(&self, db: &RootDatabase) -> Option<String> {
101 let node = self.node(db)?;
102 fn doc_comments<N: ast::DocCommentsOwner>(node: &N) -> Option<String> {
103 let comments = node.doc_comment_text();
104 if comments.is_empty() {
112 .visit(doc_comments::<ast::FnDef>)
113 .visit(doc_comments::<ast::StructDef>)
114 .visit(doc_comments::<ast::EnumDef>)
115 .visit(doc_comments::<ast::TraitDef>)
116 .visit(doc_comments::<ast::Module>)
117 .visit(doc_comments::<ast::TypeDef>)
118 .visit(doc_comments::<ast::ConstDef>)
119 .visit(doc_comments::<ast::StaticDef>)
123 /// Get a description of this node.
125 /// e.g. `struct Name`, `enum Name`, `fn Name`
126 fn description(&self, db: &RootDatabase) -> Option<String> {
127 // TODO: After type inference is done, add type information to improve the output
128 let node = self.node(db)?;
130 fn visit_node<T>(node: &T, label: &str) -> Option<String>
132 T: ast::NameOwner + ast::VisibilityOwner,
134 let mut string = node
136 .map(|v| format!("{} ", v.syntax().text()))
137 .unwrap_or_default();
138 string.push_str(label);
139 node.name()?.syntax().text().push_to(&mut string);
144 .visit(|node: &ast::FnDef| visit_node(node, "fn "))
145 .visit(|node: &ast::StructDef| visit_node(node, "struct "))
146 .visit(|node: &ast::EnumDef| visit_node(node, "enum "))
147 .visit(|node: &ast::TraitDef| visit_node(node, "trait "))
148 .visit(|node: &ast::Module| visit_node(node, "mod "))
149 .visit(|node: &ast::TypeDef| visit_node(node, "type "))
150 .visit(|node: &ast::ConstDef| visit_node(node, "const "))
151 .visit(|node: &ast::StaticDef| visit_node(node, "static "))
158 use ra_syntax::TextRange;
159 use crate::mock_analysis::{single_file_with_position, single_file_with_range};
162 fn hover_shows_type_of_an_expression() {
163 let (analysis, position) = single_file_with_position(
165 pub fn foo() -> u32 { 1 }
168 let foo_test = foo()<|>;
172 let hover = analysis.hover(position).unwrap().unwrap();
173 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
174 assert_eq!(hover.info, "u32");
178 fn hover_for_local_variable() {
179 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
180 let hover = analysis.hover(position).unwrap().unwrap();
181 assert_eq!(hover.info, "i32");
185 fn hover_for_local_variable_pat() {
186 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
187 let hover = analysis.hover(position).unwrap().unwrap();
188 assert_eq!(hover.info, "i32");
192 fn test_type_of_for_function() {
193 let (analysis, range) = single_file_with_range(
195 pub fn foo() -> u32 { 1 };
198 let foo_test = <|>foo()<|>;
203 let type_name = analysis.type_of(range).unwrap().unwrap();
204 assert_eq!("u32", &type_name);
207 // FIXME: improve type_of to make this work
209 fn test_type_of_for_expr_1() {
210 let (analysis, range) = single_file_with_range(
213 let foo = <|>1 + foo_test<|>;
218 let type_name = analysis.type_of(range).unwrap().unwrap();
219 assert_eq!("[unknown]", &type_name);
223 fn test_type_of_for_expr_2() {
224 let (analysis, range) = single_file_with_range(
228 let bar = <|>1 + foo<|>;
233 let type_name = analysis.type_of(range).unwrap().unwrap();
234 assert_eq!("usize", &type_name);