diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-05 14:42:10 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-05 14:42:10 +0000 |
commit | b1c86e686c186f75cf8c7c5d61fc12054c2ccf15 (patch) | |
tree | 232dc42dc6e42812f1b6bf4d33b2b9cf05465792 /crates/ra_analysis/src | |
parent | 8d51b02362109d71355aed63d48b5e7ccd0e51f4 (diff) | |
parent | bdbdade036fe71c4438f931f450beb711d1379ed (diff) |
Merge #435
435: Refactor hover r=matklad a=matklad
Primaraly this moves `hover` to `ra_analysis`, so that we finally can write tests for it!
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_analysis/src')
-rw-r--r-- | crates/ra_analysis/src/hover.rs | 176 | ||||
-rw-r--r-- | crates/ra_analysis/src/imp.rs | 118 | ||||
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 21 |
3 files changed, 195 insertions, 120 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 @@ | |||
1 | use ra_db::{Cancelable, SyntaxDatabase}; | ||
2 | use ra_syntax::{ | ||
3 | AstNode, SyntaxNode, | ||
4 | ast::{self, NameOwner}, | ||
5 | algo::{find_covering_node, visit::{visitor, Visitor}}, | ||
6 | }; | ||
7 | |||
8 | use crate::{db::RootDatabase, RangeInfo, FilePosition, FileRange, NavigationTarget}; | ||
9 | |||
10 | pub(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 | |||
40 | pub(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`. | ||
56 | fn 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 | |||
67 | impl 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)] | ||
156 | mod 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 | } | ||
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index eae73c2c4..e2871451c 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
@@ -8,11 +8,10 @@ use hir::{ | |||
8 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; | 8 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; |
9 | use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity}; | 9 | use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity}; |
10 | use ra_syntax::{ | 10 | use ra_syntax::{ |
11 | algo::{find_covering_node, visit::{visitor, Visitor}}, | 11 | ast::{self, ArgListOwner, Expr, NameOwner}, |
12 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, | ||
13 | AstNode, SourceFileNode, | 12 | AstNode, SourceFileNode, |
14 | SyntaxKind::*, | 13 | SyntaxKind::*, |
15 | SyntaxNode, SyntaxNodeRef, TextRange, TextUnit, | 14 | SyntaxNodeRef, TextRange, TextUnit, |
16 | }; | 15 | }; |
17 | 16 | ||
18 | use crate::{ | 17 | use crate::{ |
@@ -256,18 +255,6 @@ impl db::RootDatabase { | |||
256 | Ok(Some((binding, descr))) | 255 | Ok(Some((binding, descr))) |
257 | } | 256 | } |
258 | } | 257 | } |
259 | pub(crate) fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { | ||
260 | let result = match (nav.description(self), nav.docs(self)) { | ||
261 | (Some(desc), Some(docs)) => { | ||
262 | Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs) | ||
263 | } | ||
264 | (Some(desc), None) => Some("```rust\n".to_string() + &*desc + "\n```"), | ||
265 | (None, Some(docs)) => Some(docs), | ||
266 | _ => None, | ||
267 | }; | ||
268 | |||
269 | Ok(result) | ||
270 | } | ||
271 | 258 | ||
272 | pub(crate) fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 259 | pub(crate) fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { |
273 | let syntax = self.source_file(file_id); | 260 | let syntax = self.source_file(file_id); |
@@ -410,19 +397,6 @@ impl db::RootDatabase { | |||
410 | Ok(None) | 397 | Ok(None) |
411 | } | 398 | } |
412 | 399 | ||
413 | pub(crate) fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { | ||
414 | let file = self.source_file(frange.file_id); | ||
415 | let syntax = file.syntax(); | ||
416 | let node = find_covering_node(syntax, frange.range); | ||
417 | let parent_fn = ctry!(node.ancestors().find_map(FnDef::cast)); | ||
418 | let function = ctry!(source_binder::function_from_source( | ||
419 | self, | ||
420 | frange.file_id, | ||
421 | parent_fn | ||
422 | )?); | ||
423 | let infer = function.infer(self)?; | ||
424 | Ok(infer.type_of_node(node).map(|t| t.to_string())) | ||
425 | } | ||
426 | pub(crate) fn rename( | 400 | pub(crate) fn rename( |
427 | &self, | 401 | &self, |
428 | position: FilePosition, | 402 | position: FilePosition, |
@@ -506,91 +480,3 @@ impl<'a> FnCallNode<'a> { | |||
506 | } | 480 | } |
507 | } | 481 | } |
508 | } | 482 | } |
509 | |||
510 | impl NavigationTarget { | ||
511 | fn node(&self, db: &db::RootDatabase) -> Option<SyntaxNode> { | ||
512 | let source_file = db.source_file(self.file_id); | ||
513 | let source_file = source_file.syntax(); | ||
514 | let node = source_file | ||
515 | .descendants() | ||
516 | .find(|node| node.kind() == self.kind && node.range() == self.range)? | ||
517 | .owned(); | ||
518 | Some(node) | ||
519 | } | ||
520 | |||
521 | fn docs(&self, db: &db::RootDatabase) -> Option<String> { | ||
522 | let node = self.node(db)?; | ||
523 | let node = node.borrowed(); | ||
524 | fn doc_comments<'a, N: ast::DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
525 | let comments = node.doc_comment_text(); | ||
526 | if comments.is_empty() { | ||
527 | None | ||
528 | } else { | ||
529 | Some(comments) | ||
530 | } | ||
531 | } | ||
532 | |||
533 | visitor() | ||
534 | .visit(doc_comments::<ast::FnDef>) | ||
535 | .visit(doc_comments::<ast::StructDef>) | ||
536 | .visit(doc_comments::<ast::EnumDef>) | ||
537 | .visit(doc_comments::<ast::TraitDef>) | ||
538 | .visit(doc_comments::<ast::Module>) | ||
539 | .visit(doc_comments::<ast::TypeDef>) | ||
540 | .visit(doc_comments::<ast::ConstDef>) | ||
541 | .visit(doc_comments::<ast::StaticDef>) | ||
542 | .accept(node)? | ||
543 | } | ||
544 | |||
545 | /// Get a description of this node. | ||
546 | /// | ||
547 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
548 | fn description(&self, db: &db::RootDatabase) -> Option<String> { | ||
549 | // TODO: After type inference is done, add type information to improve the output | ||
550 | let node = self.node(db)?; | ||
551 | let node = node.borrowed(); | ||
552 | // TODO: Refactor to be have less repetition | ||
553 | visitor() | ||
554 | .visit(|node: ast::FnDef| { | ||
555 | let mut string = "fn ".to_string(); | ||
556 | node.name()?.syntax().text().push_to(&mut string); | ||
557 | Some(string) | ||
558 | }) | ||
559 | .visit(|node: ast::StructDef| { | ||
560 | let mut string = "struct ".to_string(); | ||
561 | node.name()?.syntax().text().push_to(&mut string); | ||
562 | Some(string) | ||
563 | }) | ||
564 | .visit(|node: ast::EnumDef| { | ||
565 | let mut string = "enum ".to_string(); | ||
566 | node.name()?.syntax().text().push_to(&mut string); | ||
567 | Some(string) | ||
568 | }) | ||
569 | .visit(|node: ast::TraitDef| { | ||
570 | let mut string = "trait ".to_string(); | ||
571 | node.name()?.syntax().text().push_to(&mut string); | ||
572 | Some(string) | ||
573 | }) | ||
574 | .visit(|node: ast::Module| { | ||
575 | let mut string = "mod ".to_string(); | ||
576 | node.name()?.syntax().text().push_to(&mut string); | ||
577 | Some(string) | ||
578 | }) | ||
579 | .visit(|node: ast::TypeDef| { | ||
580 | let mut string = "type ".to_string(); | ||
581 | node.name()?.syntax().text().push_to(&mut string); | ||
582 | Some(string) | ||
583 | }) | ||
584 | .visit(|node: ast::ConstDef| { | ||
585 | let mut string = "const ".to_string(); | ||
586 | node.name()?.syntax().text().push_to(&mut string); | ||
587 | Some(string) | ||
588 | }) | ||
589 | .visit(|node: ast::StaticDef| { | ||
590 | let mut string = "static ".to_string(); | ||
591 | node.name()?.syntax().text().push_to(&mut string); | ||
592 | Some(string) | ||
593 | }) | ||
594 | .accept(node)? | ||
595 | } | ||
596 | } | ||
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index 1e26a2889..b068119d2 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -21,6 +21,7 @@ mod runnables; | |||
21 | 21 | ||
22 | mod extend_selection; | 22 | mod extend_selection; |
23 | mod syntax_highlighting; | 23 | mod syntax_highlighting; |
24 | mod hover; | ||
24 | 25 | ||
25 | use std::{fmt, sync::Arc}; | 26 | use std::{fmt, sync::Arc}; |
26 | 27 | ||
@@ -260,6 +261,18 @@ impl NavigationTarget { | |||
260 | } | 261 | } |
261 | } | 262 | } |
262 | 263 | ||
264 | #[derive(Debug)] | ||
265 | pub struct RangeInfo<T> { | ||
266 | pub range: TextRange, | ||
267 | pub info: T, | ||
268 | } | ||
269 | |||
270 | impl<T> RangeInfo<T> { | ||
271 | fn new(range: TextRange, info: T) -> RangeInfo<T> { | ||
272 | RangeInfo { range, info } | ||
273 | } | ||
274 | } | ||
275 | |||
263 | /// Result of "goto def" query. | 276 | /// Result of "goto def" query. |
264 | #[derive(Debug)] | 277 | #[derive(Debug)] |
265 | pub struct ReferenceResolution { | 278 | pub struct ReferenceResolution { |
@@ -390,9 +403,9 @@ impl Analysis { | |||
390 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { | 403 | pub fn find_all_refs(&self, position: FilePosition) -> Cancelable<Vec<(FileId, TextRange)>> { |
391 | self.db.find_all_refs(position) | 404 | self.db.find_all_refs(position) |
392 | } | 405 | } |
393 | /// Returns documentation string for a given target. | 406 | /// Returns a short text descrbing element at position. |
394 | pub fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { | 407 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<String>>> { |
395 | self.db.doc_text_for(nav) | 408 | hover::hover(&*self.db, position) |
396 | } | 409 | } |
397 | /// Returns a `mod name;` declaration which created the current module. | 410 | /// Returns a `mod name;` declaration which created the current module. |
398 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { | 411 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { |
@@ -437,7 +450,7 @@ impl Analysis { | |||
437 | } | 450 | } |
438 | /// Computes the type of the expression at the given position. | 451 | /// Computes the type of the expression at the given position. |
439 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { | 452 | pub fn type_of(&self, frange: FileRange) -> Cancelable<Option<String>> { |
440 | self.db.type_of(frange) | 453 | hover::type_of(&*self.db, frange) |
441 | } | 454 | } |
442 | /// Returns the edit required to rename reference at the position to the new | 455 | /// Returns the edit required to rename reference at the position to the new |
443 | /// name. | 456 | /// name. |