aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-01-05 14:42:10 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-01-05 14:42:10 +0000
commitb1c86e686c186f75cf8c7c5d61fc12054c2ccf15 (patch)
tree232dc42dc6e42812f1b6bf4d33b2b9cf05465792
parent8d51b02362109d71355aed63d48b5e7ccd0e51f4 (diff)
parentbdbdade036fe71c4438f931f450beb711d1379ed (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]>
-rw-r--r--crates/ra_analysis/src/hover.rs176
-rw-r--r--crates/ra_analysis/src/imp.rs118
-rw-r--r--crates/ra_analysis/src/lib.rs21
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs50
4 files changed, 204 insertions, 161 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}
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::{
8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; 8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
9use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity}; 9use ra_editor::{self, find_node_at_offset, assists, LocalEdit, Severity};
10use ra_syntax::{ 10use 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
18use crate::{ 17use 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
510impl 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
22mod extend_selection; 22mod extend_selection;
23mod syntax_highlighting; 23mod syntax_highlighting;
24mod hover;
24 25
25use std::{fmt, sync::Arc}; 26use std::{fmt, sync::Arc};
26 27
@@ -260,6 +261,18 @@ impl NavigationTarget {
260 } 261 }
261} 262}
262 263
264#[derive(Debug)]
265pub struct RangeInfo<T> {
266 pub range: TextRange,
267 pub info: T,
268}
269
270impl<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)]
265pub struct ReferenceResolution { 278pub 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.
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 2fc4d3649..ffca3f51c 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -9,7 +9,7 @@ use languageserver_types::{
9 Range, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover, 9 Range, WorkspaceEdit, ParameterInformation, ParameterLabel, SignatureInformation, Hover,
10 HoverContents, DocumentFormattingParams, DocumentHighlight, 10 HoverContents, DocumentFormattingParams, DocumentHighlight,
11}; 11};
12use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity, NavigationTarget}; 12use ra_analysis::{FileId, FoldKind, Query, RunnableKind, FileRange, FilePosition, Severity};
13use ra_syntax::{TextUnit, text_utils::intersect}; 13use ra_syntax::{TextUnit, text_utils::intersect};
14use ra_text_edit::text_utils::contains_offset_nonstrict; 14use ra_text_edit::text_utils::contains_offset_nonstrict;
15use rustc_hash::FxHashMap; 15use rustc_hash::FxHashMap;
@@ -509,36 +509,18 @@ pub fn handle_hover(
509 world: ServerWorld, 509 world: ServerWorld,
510 params: req::TextDocumentPositionParams, 510 params: req::TextDocumentPositionParams,
511) -> Result<Option<Hover>> { 511) -> Result<Option<Hover>> {
512 // TODO: Cut down on number of allocations
513 let position = params.try_conv_with(&world)?; 512 let position = params.try_conv_with(&world)?;
514 let line_index = world.analysis().file_line_index(position.file_id); 513 let info = match world.analysis().hover(position)? {
515 let rr = match world.analysis().approximately_resolve_symbol(position)? {
516 None => return Ok(None), 514 None => return Ok(None),
517 Some(it) => it, 515 Some(info) => info,
518 }; 516 };
519 let mut result = Vec::new(); 517 let line_index = world.analysis.file_line_index(position.file_id);
520 let file_id = params.text_document.try_conv_with(&world)?; 518 let range = info.range.conv_with(&line_index);
521 let file_range = FileRange { 519 let res = Hover {
522 file_id, 520 contents: HoverContents::Scalar(MarkedString::String(info.info)),
523 range: rr.reference_range, 521 range: Some(range),
524 }; 522 };
525 if let Some(type_name) = get_type(&world, file_range) { 523 Ok(Some(res))
526 result.push(type_name);
527 }
528 for nav in rr.resolves_to {
529 if let Some(docs) = get_doc_text(&world, nav) {
530 result.push(docs);
531 }
532 }
533
534 let range = rr.reference_range.conv_with(&line_index);
535 if result.len() > 0 {
536 return Ok(Some(Hover {
537 contents: HoverContents::Scalar(MarkedString::String(result.join("\n\n---\n"))),
538 range: Some(range),
539 }));
540 }
541 Ok(None)
542} 524}
543 525
544/// Test doc comment 526/// Test doc comment
@@ -762,17 +744,3 @@ fn to_diagnostic_severity(severity: Severity) -> DiagnosticSeverity {
762 WeakWarning => DiagnosticSeverity::Hint, 744 WeakWarning => DiagnosticSeverity::Hint,
763 } 745 }
764} 746}
765
766fn get_type(world: &ServerWorld, file_range: FileRange) -> Option<String> {
767 match world.analysis().type_of(file_range) {
768 Ok(result) => result,
769 _ => None,
770 }
771}
772
773fn get_doc_text(world: &ServerWorld, nav: NavigationTarget) -> Option<String> {
774 match world.analysis().doc_text_for(nav) {
775 Ok(result) => result,
776 _ => None,
777 }
778}