aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/hover.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r--crates/ra_ide/src/hover.rs93
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
3use hir::{db::AstDatabase, Adt, HasSource, HirDisplay, SourceBinder}; 3use hir::{Adt, HasSource, HirDisplay, Semantics};
4use ra_db::SourceDatabase; 4use ra_ide_db::{
5use ra_ide_db::{defs::NameDefinition, RootDatabase}; 5 defs::{classify_name, NameDefinition},
6 RootDatabase,
7};
6use ra_syntax::{ 8use 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
14use crate::{ 16use 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
145pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { 146pub(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
199pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { 203pub(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)]
221mod tests { 223mod 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 "