aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_analysis/src/hover.rs')
-rw-r--r--crates/ra_analysis/src/hover.rs176
1 files changed, 176 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/hover.rs b/crates/ra_analysis/src/hover.rs
new file mode 100644
index 000000000..766fa0547
--- /dev/null
+++ b/crates/ra_analysis/src/hover.rs
@@ -0,0 +1,176 @@
1use ra_db::{Cancelable, SyntaxDatabase};
2use ra_syntax::{
3 AstNode, SyntaxNode,
4 ast::{self, NameOwner},
5 algo::{find_covering_node, visit::{visitor, Visitor}},
6};
7
8use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget};
9
10pub(crate) fn hover(
11 db: &RootDatabase,
12 position: FilePosition,
13) -> Cancelable<Option<RangeInfo<String>>> {
14 let mut res = Vec::new();
15 let range = if let Some(rr) = db.approximately_resolve_symbol(position)? {
16 for nav in rr.resolves_to {
17 res.extend(doc_text_for(db, nav)?)
18 }
19 rr.reference_range
20 } else {
21 let file = db.source_file(position.file_id);
22 let expr: ast::Expr = ctry!(ra_editor::find_node_at_offset(
23 file.syntax(),
24 position.offset
25 ));
26 let frange = FileRange {
27 file_id: position.file_id,
28 range: expr.syntax().range(),
29 };
30 res.extend(type_of(db, frange)?);
31 expr.syntax().range()
32 };
33 if res.is_empty() {
34 return Ok(None);
35 }
36 let res = RangeInfo::new(range, res.join("\n\n---\n"));
37 Ok(Some(res))
38}
39
40pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Cancelable<Option<String>> {
41 let file = db.source_file(frange.file_id);
42 let syntax = file.syntax();
43 let node = find_covering_node(syntax, frange.range);
44 let parent_fn = ctry!(node.ancestors().find_map(ast::FnDef::cast));
45 let function = ctry!(hir::source_binder::function_from_source(
46 db,
47 frange.file_id,
48 parent_fn
49 )?);
50 let infer = function.infer(db)?;
51 Ok(infer.type_of_node(node).map(|t| t.to_string()))
52}
53
54// FIXME: this should not really use navigation target. Rather, approximatelly
55// resovled symbol should return a `DefId`.
56fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Cancelable<Option<String>> {
57 let result = match (nav.description(db), nav.docs(db)) {
58 (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs),
59 (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"),
60 (None, Some(docs)) => Some(docs),
61 _ => None,
62 };
63
64 Ok(result)
65}
66
67impl NavigationTarget {
68 fn node(&self, db: &RootDatabase) -> Option<SyntaxNode> {
69 let source_file = db.source_file(self.file_id);
70 let source_file = source_file.syntax();
71 let node = source_file
72 .descendants()
73 .find(|node| node.kind() == self.kind && node.range() == self.range)?
74 .owned();
75 Some(node)
76 }
77
78 fn docs(&self, db: &RootDatabase) -> Option<String> {
79 let node = self.node(db)?;
80 let node = node.borrowed();
81 fn doc_comments<'a, N: ast::DocCommentsOwner<'a>>(node: N) -> Option<String> {
82 let comments = node.doc_comment_text();
83 if comments.is_empty() {
84 None
85 } else {
86 Some(comments)
87 }
88 }
89
90 visitor()
91 .visit(doc_comments::<ast::FnDef>)
92 .visit(doc_comments::<ast::StructDef>)
93 .visit(doc_comments::<ast::EnumDef>)
94 .visit(doc_comments::<ast::TraitDef>)
95 .visit(doc_comments::<ast::Module>)
96 .visit(doc_comments::<ast::TypeDef>)
97 .visit(doc_comments::<ast::ConstDef>)
98 .visit(doc_comments::<ast::StaticDef>)
99 .accept(node)?
100 }
101
102 /// Get a description of this node.
103 ///
104 /// e.g. `struct Name`, `enum Name`, `fn Name`
105 fn description(&self, db: &RootDatabase) -> Option<String> {
106 // TODO: After type inference is done, add type information to improve the output
107 let node = self.node(db)?;
108 let node = node.borrowed();
109 // TODO: Refactor to be have less repetition
110 visitor()
111 .visit(|node: ast::FnDef| {
112 let mut string = "fn ".to_string();
113 node.name()?.syntax().text().push_to(&mut string);
114 Some(string)
115 })
116 .visit(|node: ast::StructDef| {
117 let mut string = "struct ".to_string();
118 node.name()?.syntax().text().push_to(&mut string);
119 Some(string)
120 })
121 .visit(|node: ast::EnumDef| {
122 let mut string = "enum ".to_string();
123 node.name()?.syntax().text().push_to(&mut string);
124 Some(string)
125 })
126 .visit(|node: ast::TraitDef| {
127 let mut string = "trait ".to_string();
128 node.name()?.syntax().text().push_to(&mut string);
129 Some(string)
130 })
131 .visit(|node: ast::Module| {
132 let mut string = "mod ".to_string();
133 node.name()?.syntax().text().push_to(&mut string);
134 Some(string)
135 })
136 .visit(|node: ast::TypeDef| {
137 let mut string = "type ".to_string();
138 node.name()?.syntax().text().push_to(&mut string);
139 Some(string)
140 })
141 .visit(|node: ast::ConstDef| {
142 let mut string = "const ".to_string();
143 node.name()?.syntax().text().push_to(&mut string);
144 Some(string)
145 })
146 .visit(|node: ast::StaticDef| {
147 let mut string = "static ".to_string();
148 node.name()?.syntax().text().push_to(&mut string);
149 Some(string)
150 })
151 .accept(node)?
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use ra_syntax::TextRange;
158
159 use crate::mock_analysis::single_file_with_position;
160
161 #[test]
162 fn hover_shows_type_of_an_expression() {
163 let (analysis, position) = single_file_with_position(
164 "
165 pub fn foo() -> u32 { 1 }
166
167 fn main() {
168 let foo_test = foo()<|>;
169 }
170 ",
171 );
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");
175 }
176}