aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/hover.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-01-08 19:30:56 +0000
committerAleksey Kladov <[email protected]>2019-01-08 19:30:56 +0000
commit6bca91af532d79abbced5b151cb4188ff8625c04 (patch)
tree6f61b6358bb99925adf71ade2ecb12e38e4ef51f /crates/ra_analysis/src/hover.rs
parentfa3c9ce3921b6a3f67222bf4f9b4efdf4f11c2a5 (diff)
rename ra_analysis -> ra_ide_api
Diffstat (limited to 'crates/ra_analysis/src/hover.rs')
-rw-r--r--crates/ra_analysis/src/hover.rs257
1 files changed, 0 insertions, 257 deletions
diff --git a/crates/ra_analysis/src/hover.rs b/crates/ra_analysis/src/hover.rs
deleted file mode 100644
index 475524ee1..000000000
--- a/crates/ra_analysis/src/hover.rs
+++ /dev/null
@@ -1,257 +0,0 @@
1use ra_db::{Cancelable, SyntaxDatabase};
2use ra_syntax::{
3 AstNode, SyntaxNode, TreePtr,
4 ast::{self, NameOwner},
5 algo::{find_covering_node, find_node_at_offset, find_leaf_at_offset, 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 file = db.source_file(position.file_id);
15 let mut res = Vec::new();
16
17 let mut range = None;
18 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
19 let navs = crate::goto_defenition::reference_defenition(db, position.file_id, name_ref)?;
20 for nav in navs {
21 res.extend(doc_text_for(db, nav)?)
22 }
23 if !res.is_empty() {
24 range = Some(name_ref.syntax().range())
25 }
26 }
27 if range.is_none() {
28 let node = find_leaf_at_offset(file.syntax(), position.offset).find_map(|leaf| {
29 leaf.ancestors()
30 .find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())
31 });
32 let node = ctry!(node);
33 let frange = FileRange {
34 file_id: position.file_id,
35 range: node.range(),
36 };
37 res.extend(type_of(db, frange)?);
38 range = Some(node.range());
39 };
40
41 let range = ctry!(range);
42 if res.is_empty() {
43 return Ok(None);
44 }
45 let res = RangeInfo::new(range, res.join("\n\n---\n"));
46 Ok(Some(res))
47}
48
49pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Cancelable<Option<String>> {
50 let file = db.source_file(frange.file_id);
51 let syntax = file.syntax();
52 let leaf_node = find_covering_node(syntax, frange.range);
53 // if we picked identifier, expand to pattern/expression
54 let node = leaf_node
55 .ancestors()
56 .take_while(|it| it.range() == leaf_node.range())
57 .find(|&it| ast::Expr::cast(it).is_some() || ast::Pat::cast(it).is_some())
58 .unwrap_or(leaf_node);
59 let parent_fn = ctry!(node.ancestors().find_map(ast::FnDef::cast));
60 let function = ctry!(hir::source_binder::function_from_source(
61 db,
62 frange.file_id,
63 parent_fn
64 )?);
65 let infer = function.infer(db)?;
66 let syntax_mapping = function.body_syntax_mapping(db)?;
67 if let Some(expr) = ast::Expr::cast(node).and_then(|e| syntax_mapping.node_expr(e)) {
68 Ok(Some(infer[expr].to_string()))
69 } else if let Some(pat) = ast::Pat::cast(node).and_then(|p| syntax_mapping.node_pat(p)) {
70 Ok(Some(infer[pat].to_string()))
71 } else {
72 Ok(None)
73 }
74}
75
76// FIXME: this should not really use navigation target. Rather, approximatelly
77// resovled symbol should return a `DefId`.
78fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Cancelable<Option<String>> {
79 let result = match (nav.description(db), nav.docs(db)) {
80 (Some(desc), Some(docs)) => Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs),
81 (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"),
82 (None, Some(docs)) => Some(docs),
83 _ => None,
84 };
85
86 Ok(result)
87}
88
89impl NavigationTarget {
90 fn node(&self, db: &RootDatabase) -> Option<TreePtr<SyntaxNode>> {
91 let source_file = db.source_file(self.file_id);
92 let source_file = source_file.syntax();
93 let node = source_file
94 .descendants()
95 .find(|node| node.kind() == self.kind && node.range() == self.range)?
96 .to_owned();
97 Some(node)
98 }
99
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() {
105 None
106 } else {
107 Some(comments)
108 }
109 }
110
111 visitor()
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>)
120 .accept(&node)?
121 }
122
123 /// Get a description of this node.
124 ///
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)?;
129 // TODO: Refactor to be have less repetition
130 visitor()
131 .visit(|node: &ast::FnDef| {
132 let mut string = "fn ".to_string();
133 node.name()?.syntax().text().push_to(&mut string);
134 Some(string)
135 })
136 .visit(|node: &ast::StructDef| {
137 let mut string = "struct ".to_string();
138 node.name()?.syntax().text().push_to(&mut string);
139 Some(string)
140 })
141 .visit(|node: &ast::EnumDef| {
142 let mut string = "enum ".to_string();
143 node.name()?.syntax().text().push_to(&mut string);
144 Some(string)
145 })
146 .visit(|node: &ast::TraitDef| {
147 let mut string = "trait ".to_string();
148 node.name()?.syntax().text().push_to(&mut string);
149 Some(string)
150 })
151 .visit(|node: &ast::Module| {
152 let mut string = "mod ".to_string();
153 node.name()?.syntax().text().push_to(&mut string);
154 Some(string)
155 })
156 .visit(|node: &ast::TypeDef| {
157 let mut string = "type ".to_string();
158 node.name()?.syntax().text().push_to(&mut string);
159 Some(string)
160 })
161 .visit(|node: &ast::ConstDef| {
162 let mut string = "const ".to_string();
163 node.name()?.syntax().text().push_to(&mut string);
164 Some(string)
165 })
166 .visit(|node: &ast::StaticDef| {
167 let mut string = "static ".to_string();
168 node.name()?.syntax().text().push_to(&mut string);
169 Some(string)
170 })
171 .accept(&node)?
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use ra_syntax::TextRange;
178 use crate::mock_analysis::{single_file_with_position, single_file_with_range};
179
180 #[test]
181 fn hover_shows_type_of_an_expression() {
182 let (analysis, position) = single_file_with_position(
183 "
184 pub fn foo() -> u32 { 1 }
185
186 fn main() {
187 let foo_test = foo()<|>;
188 }
189 ",
190 );
191 let hover = analysis.hover(position).unwrap().unwrap();
192 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
193 assert_eq!(hover.info, "u32");
194 }
195
196 #[test]
197 fn hover_for_local_variable() {
198 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
199 let hover = analysis.hover(position).unwrap().unwrap();
200 assert_eq!(hover.info, "i32");
201 }
202
203 #[test]
204 fn hover_for_local_variable_pat() {
205 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
206 let hover = analysis.hover(position).unwrap().unwrap();
207 assert_eq!(hover.info, "i32");
208 }
209
210 #[test]
211 fn test_type_of_for_function() {
212 let (analysis, range) = single_file_with_range(
213 "
214 pub fn foo() -> u32 { 1 };
215
216 fn main() {
217 let foo_test = <|>foo()<|>;
218 }
219 ",
220 );
221
222 let type_name = analysis.type_of(range).unwrap().unwrap();
223 assert_eq!("u32", &type_name);
224 }
225
226 // FIXME: improve type_of to make this work
227 #[test]
228 fn test_type_of_for_expr_1() {
229 let (analysis, range) = single_file_with_range(
230 "
231 fn main() {
232 let foo = <|>1 + foo_test<|>;
233 }
234 ",
235 );
236
237 let type_name = analysis.type_of(range).unwrap().unwrap();
238 assert_eq!("[unknown]", &type_name);
239 }
240
241 // FIXME: improve type_of to make this work
242 #[test]
243 fn test_type_of_for_expr_2() {
244 let (analysis, range) = single_file_with_range(
245 "
246 fn main() {
247 let foo: usize = 1;
248 let bar = <|>1 + foo_test<|>;
249 }
250 ",
251 );
252
253 let type_name = analysis.type_of(range).unwrap().unwrap();
254 assert_eq!("[unknown]", &type_name);
255 }
256
257}