diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 93 |
1 files changed, 70 insertions, 23 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 1c6ca36df..177038e20 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,8 +1,10 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use hir::{db::AstDatabase, Adt, HasSource, HirDisplay, SourceBinder}; | 3 | use hir::{Adt, HasSource, HirDisplay, Semantics}; |
4 | use ra_db::SourceDatabase; | 4 | use ra_ide_db::{ |
5 | use ra_ide_db::{defs::NameDefinition, RootDatabase}; | 5 | defs::{classify_name, NameDefinition}, |
6 | RootDatabase, | ||
7 | }; | ||
6 | use ra_syntax::{ | 8 | use ra_syntax::{ |
7 | algo::find_covering_element, | 9 | algo::find_covering_element, |
8 | ast::{self, DocCommentsOwner}, | 10 | ast::{self, DocCommentsOwner}, |
@@ -13,8 +15,7 @@ use ra_syntax::{ | |||
13 | 15 | ||
14 | use crate::{ | 16 | use crate::{ |
15 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 17 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
16 | expand::{descend_into_macros, original_range}, | 18 | references::classify_name_ref, |
17 | references::{classify_name, classify_name_ref}, | ||
18 | FilePosition, FileRange, RangeInfo, | 19 | FilePosition, FileRange, RangeInfo, |
19 | }; | 20 | }; |
20 | 21 | ||
@@ -143,25 +144,25 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: NameDefinition) -> Option<S | |||
143 | } | 144 | } |
144 | 145 | ||
145 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | 146 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { |
146 | let file = db.parse_or_expand(position.file_id.into())?; | 147 | let sema = Semantics::new(db); |
148 | let file = sema.parse(position.file_id).syntax().clone(); | ||
147 | let token = pick_best(file.token_at_offset(position.offset))?; | 149 | let token = pick_best(file.token_at_offset(position.offset))?; |
148 | let token = descend_into_macros(db, position.file_id, token); | 150 | let token = sema.descend_into_macros(token); |
149 | 151 | ||
150 | let mut res = HoverResult::new(); | 152 | let mut res = HoverResult::new(); |
151 | 153 | ||
152 | let mut sb = SourceBinder::new(db); | ||
153 | if let Some((node, name_kind)) = match_ast! { | 154 | if let Some((node, name_kind)) = match_ast! { |
154 | match (token.value.parent()) { | 155 | match (token.parent()) { |
155 | ast::NameRef(name_ref) => { | 156 | ast::NameRef(name_ref) => { |
156 | classify_name_ref(&mut sb, token.with_value(&name_ref)).map(|d| (name_ref.syntax().clone(), d)) | 157 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d)) |
157 | }, | 158 | }, |
158 | ast::Name(name) => { | 159 | ast::Name(name) => { |
159 | classify_name(&mut sb, token.with_value(&name)).map(|d| (name.syntax().clone(), d)) | 160 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d)) |
160 | }, | 161 | }, |
161 | _ => None, | 162 | _ => None, |
162 | } | 163 | } |
163 | } { | 164 | } { |
164 | let range = original_range(db, token.with_value(&node)).range; | 165 | let range = sema.original_range(&node).range; |
165 | res.extend(hover_text_from_name_kind(db, name_kind)); | 166 | res.extend(hover_text_from_name_kind(db, name_kind)); |
166 | 167 | ||
167 | if !res.is_empty() { | 168 | if !res.is_empty() { |
@@ -170,11 +171,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
170 | } | 171 | } |
171 | 172 | ||
172 | let node = token | 173 | let node = token |
173 | .value | ||
174 | .ancestors() | 174 | .ancestors() |
175 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | 175 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; |
176 | 176 | ||
177 | let frange = original_range(db, token.with_value(&node)); | 177 | // FIXME: Currently `hover::typeof` do not work inside |
178 | // macro expansion such that if the hover range is pointing to | ||
179 | // a string literal, the following type_of will return None. | ||
180 | // See also `test_hover_through_literal_string_in_macro` | ||
181 | let frange = sema.original_range(&node); | ||
178 | res.extend(type_of(db, frange).map(rust_code_markup)); | 182 | res.extend(type_of(db, frange).map(rust_code_markup)); |
179 | if res.is_empty() { | 183 | if res.is_empty() { |
180 | return None; | 184 | return None; |
@@ -197,19 +201,17 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | |||
197 | } | 201 | } |
198 | 202 | ||
199 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | 203 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { |
200 | let parse = db.parse(frange.file_id); | 204 | let sema = Semantics::new(db); |
201 | let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); | 205 | let source_file = sema.parse(frange.file_id); |
206 | let leaf_node = find_covering_element(source_file.syntax(), frange.range); | ||
202 | // if we picked identifier, expand to pattern/expression | 207 | // if we picked identifier, expand to pattern/expression |
203 | let node = leaf_node | 208 | let node = leaf_node |
204 | .ancestors() | 209 | .ancestors() |
205 | .take_while(|it| it.text_range() == leaf_node.text_range()) | 210 | .take_while(|it| it.text_range() == leaf_node.text_range()) |
206 | .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; | 211 | .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; |
207 | let analyzer = | 212 | let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| sema.type_of_expr(&e)) { |
208 | hir::SourceAnalyzer::new(db, hir::InFile::new(frange.file_id.into(), &node), None); | ||
209 | let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) | ||
210 | { | ||
211 | ty | 213 | ty |
212 | } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) { | 214 | } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| sema.type_of_pat(&p)) { |
213 | ty | 215 | ty |
214 | } else { | 216 | } else { |
215 | return None; | 217 | return None; |
@@ -219,11 +221,12 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | |||
219 | 221 | ||
220 | #[cfg(test)] | 222 | #[cfg(test)] |
221 | mod tests { | 223 | mod tests { |
224 | use ra_db::FileLoader; | ||
225 | use ra_syntax::TextRange; | ||
226 | |||
222 | use crate::mock_analysis::{ | 227 | use crate::mock_analysis::{ |
223 | analysis_and_position, single_file_with_position, single_file_with_range, | 228 | analysis_and_position, single_file_with_position, single_file_with_range, |
224 | }; | 229 | }; |
225 | use ra_db::FileLoader; | ||
226 | use ra_syntax::TextRange; | ||
227 | 230 | ||
228 | fn trim_markup(s: &str) -> &str { | 231 | fn trim_markup(s: &str) -> &str { |
229 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") | 232 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") |
@@ -251,6 +254,11 @@ mod tests { | |||
251 | content[hover.range].to_string() | 254 | content[hover.range].to_string() |
252 | } | 255 | } |
253 | 256 | ||
257 | fn check_hover_no_result(fixture: &str) { | ||
258 | let (analysis, position) = analysis_and_position(fixture); | ||
259 | assert!(analysis.hover(position).unwrap().is_none()); | ||
260 | } | ||
261 | |||
254 | #[test] | 262 | #[test] |
255 | fn hover_shows_type_of_an_expression() { | 263 | fn hover_shows_type_of_an_expression() { |
256 | let (analysis, position) = single_file_with_position( | 264 | let (analysis, position) = single_file_with_position( |
@@ -755,6 +763,45 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
755 | } | 763 | } |
756 | 764 | ||
757 | #[test] | 765 | #[test] |
766 | fn test_hover_through_expr_in_macro_recursive() { | ||
767 | let hover_on = check_hover_result( | ||
768 | " | ||
769 | //- /lib.rs | ||
770 | macro_rules! id_deep { | ||
771 | ($($tt:tt)*) => { $($tt)* } | ||
772 | } | ||
773 | macro_rules! id { | ||
774 | ($($tt:tt)*) => { id_deep!($($tt)*) } | ||
775 | } | ||
776 | fn foo(bar:u32) { | ||
777 | let a = id!(ba<|>r); | ||
778 | } | ||
779 | ", | ||
780 | &["u32"], | ||
781 | ); | ||
782 | |||
783 | assert_eq!(hover_on, "bar") | ||
784 | } | ||
785 | |||
786 | #[test] | ||
787 | fn test_hover_through_literal_string_in_macro() { | ||
788 | // FIXME: Currently `hover::type_of` do not work inside | ||
789 | // macro expansion | ||
790 | check_hover_no_result( | ||
791 | r#" | ||
792 | //- /lib.rs | ||
793 | macro_rules! arr { | ||
794 | ($($tt:tt)*) => { [$($tt)*)] } | ||
795 | } | ||
796 | fn foo() { | ||
797 | let mastered_for_itunes = ""; | ||
798 | let _ = arr!("Tr<|>acks", &mastered_for_itunes); | ||
799 | } | ||
800 | "#, | ||
801 | ); | ||
802 | } | ||
803 | |||
804 | #[test] | ||
758 | fn test_hover_non_ascii_space_doc() { | 805 | fn test_hover_non_ascii_space_doc() { |
759 | check_hover_result( | 806 | check_hover_result( |
760 | " | 807 | " |