diff options
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 126 |
1 files changed, 64 insertions, 62 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 260a7b869..35e39f965 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -6,14 +6,13 @@ use ra_syntax::{ | |||
6 | algo::find_covering_element, | 6 | algo::find_covering_element, |
7 | ast::{self, DocCommentsOwner}, | 7 | ast::{self, DocCommentsOwner}, |
8 | match_ast, AstNode, | 8 | match_ast, AstNode, |
9 | SyntaxKind::*, | ||
10 | SyntaxToken, TokenAtOffset, | ||
9 | }; | 11 | }; |
10 | 12 | ||
11 | use crate::{ | 13 | use crate::{ |
12 | db::RootDatabase, | 14 | db::RootDatabase, |
13 | display::{ | 15 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
14 | description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, | ||
15 | rust_code_markup_with_doc, ShortLabel, | ||
16 | }, | ||
17 | expand::descend_into_macros, | 16 | expand::descend_into_macros, |
18 | references::{classify_name, classify_name_ref, NameKind, NameKind::*}, | 17 | references::{classify_name, classify_name_ref, NameKind, NameKind::*}, |
19 | FilePosition, FileRange, RangeInfo, | 18 | FilePosition, FileRange, RangeInfo, |
@@ -93,11 +92,7 @@ fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> { | |||
93 | } | 92 | } |
94 | } | 93 | } |
95 | 94 | ||
96 | fn hover_text_from_name_kind( | 95 | fn hover_text_from_name_kind(db: &RootDatabase, name_kind: NameKind) -> Option<String> { |
97 | db: &RootDatabase, | ||
98 | name_kind: NameKind, | ||
99 | no_fallback: &mut bool, | ||
100 | ) -> Option<String> { | ||
101 | return match name_kind { | 96 | return match name_kind { |
102 | Macro(it) => { | 97 | Macro(it) => { |
103 | let src = it.source(db); | 98 | let src = it.source(db); |
@@ -133,12 +128,8 @@ fn hover_text_from_name_kind( | |||
133 | hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), | 128 | hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), |
134 | hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), | 129 | hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), |
135 | }, | 130 | }, |
136 | Local(_) => { | 131 | Local(_) => None, |
137 | // Hover for these shows type names | 132 | TypeParam(_) | SelfType(_) => { |
138 | *no_fallback = true; | ||
139 | None | ||
140 | } | ||
141 | GenericParam(_) | SelfType(_) => { | ||
142 | // FIXME: Hover for generic param | 133 | // FIXME: Hover for generic param |
143 | None | 134 | None |
144 | } | 135 | } |
@@ -156,68 +147,55 @@ fn hover_text_from_name_kind( | |||
156 | 147 | ||
157 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | 148 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { |
158 | let file = db.parse_or_expand(position.file_id.into())?; | 149 | let file = db.parse_or_expand(position.file_id.into())?; |
159 | let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; | 150 | let token = pick_best(file.token_at_offset(position.offset))?; |
160 | let token = descend_into_macros(db, position.file_id, token); | 151 | let token = descend_into_macros(db, position.file_id, token); |
161 | 152 | ||
162 | let mut res = HoverResult::new(); | 153 | let mut res = HoverResult::new(); |
163 | 154 | ||
164 | let mut range = match_ast! { | 155 | if let Some((range, name_kind)) = match_ast! { |
165 | match (token.value.parent()) { | 156 | match (token.value.parent()) { |
166 | ast::NameRef(name_ref) => { | 157 | ast::NameRef(name_ref) => { |
167 | let mut no_fallback = false; | 158 | classify_name_ref(db, token.with_value(&name_ref)).map(|d| (name_ref.syntax().text_range(), d.kind)) |
168 | if let Some(name_kind) = | ||
169 | classify_name_ref(db, token.with_value(&name_ref)).map(|d| d.kind) | ||
170 | { | ||
171 | res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback)) | ||
172 | } | ||
173 | |||
174 | if res.is_empty() && !no_fallback { | ||
175 | // Fallback index based approach: | ||
176 | let symbols = crate::symbol_index::index_resolve(db, &name_ref); | ||
177 | for sym in symbols { | ||
178 | let docs = docs_from_symbol(db, &sym); | ||
179 | let desc = description_from_symbol(db, &sym); | ||
180 | res.extend(hover_text(docs, desc)); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | if !res.is_empty() { | ||
185 | Some(name_ref.syntax().text_range()) | ||
186 | } else { | ||
187 | None | ||
188 | } | ||
189 | }, | 159 | }, |
190 | ast::Name(name) => { | 160 | ast::Name(name) => { |
191 | if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| d.kind) { | 161 | classify_name(db, token.with_value(&name)).map(|d| (name.syntax().text_range(), d.kind)) |
192 | res.extend(hover_text_from_name_kind(db, name_kind, &mut true)); | ||
193 | } | ||
194 | |||
195 | if !res.is_empty() { | ||
196 | Some(name.syntax().text_range()) | ||
197 | } else { | ||
198 | None | ||
199 | } | ||
200 | }, | 162 | }, |
201 | _ => None, | 163 | _ => None, |
202 | } | 164 | } |
203 | }; | 165 | } { |
166 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
204 | 167 | ||
205 | if range.is_none() { | 168 | if !res.is_empty() { |
206 | let node = token.value.ancestors().find(|n| { | 169 | return Some(RangeInfo::new(range, res)); |
207 | ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some() | 170 | } |
208 | })?; | 171 | } |
209 | let frange = FileRange { file_id: position.file_id, range: node.text_range() }; | ||
210 | res.extend(type_of(db, frange).map(rust_code_markup)); | ||
211 | range = Some(node.text_range()); | ||
212 | }; | ||
213 | 172 | ||
214 | let range = range?; | 173 | let node = token |
174 | .value | ||
175 | .ancestors() | ||
176 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
177 | let frange = FileRange { file_id: position.file_id, range: node.text_range() }; | ||
178 | res.extend(type_of(db, frange).map(rust_code_markup)); | ||
215 | if res.is_empty() { | 179 | if res.is_empty() { |
216 | return None; | 180 | return None; |
217 | } | 181 | } |
182 | let range = node.text_range(); | ||
183 | |||
218 | Some(RangeInfo::new(range, res)) | 184 | Some(RangeInfo::new(range, res)) |
219 | } | 185 | } |
220 | 186 | ||
187 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | ||
188 | return tokens.max_by_key(priority); | ||
189 | fn priority(n: &SyntaxToken) -> usize { | ||
190 | match n.kind() { | ||
191 | IDENT | INT_NUMBER => 3, | ||
192 | L_PAREN | R_PAREN => 2, | ||
193 | kind if kind.is_trivia() => 0, | ||
194 | _ => 1, | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
221 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | 199 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { |
222 | let parse = db.parse(frange.file_id); | 200 | let parse = db.parse(frange.file_id); |
223 | let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); | 201 | let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); |
@@ -227,7 +205,7 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | |||
227 | .take_while(|it| it.text_range() == leaf_node.text_range()) | 205 | .take_while(|it| it.text_range() == leaf_node.text_range()) |
228 | .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; | 206 | .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; |
229 | let analyzer = | 207 | let analyzer = |
230 | hir::SourceAnalyzer::new(db, hir::Source::new(frange.file_id.into(), &node), None); | 208 | hir::SourceAnalyzer::new(db, hir::InFile::new(frange.file_id.into(), &node), None); |
231 | let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) | 209 | let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) |
232 | { | 210 | { |
233 | ty | 211 | ty |
@@ -236,7 +214,7 @@ pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | |||
236 | } else { | 214 | } else { |
237 | return None; | 215 | return None; |
238 | }; | 216 | }; |
239 | Some(ty.display(db).to_string()) | 217 | Some(ty.display_truncated(db, None).to_string()) |
240 | } | 218 | } |
241 | 219 | ||
242 | #[cfg(test)] | 220 | #[cfg(test)] |
@@ -300,7 +278,7 @@ mod tests { | |||
300 | &["pub fn foo() -> u32"], | 278 | &["pub fn foo() -> u32"], |
301 | ); | 279 | ); |
302 | 280 | ||
303 | // Multiple results | 281 | // Multiple candidates but results are ambiguous. |
304 | check_hover_result( | 282 | check_hover_result( |
305 | r#" | 283 | r#" |
306 | //- /a.rs | 284 | //- /a.rs |
@@ -321,7 +299,7 @@ mod tests { | |||
321 | let foo_test = fo<|>o(); | 299 | let foo_test = fo<|>o(); |
322 | } | 300 | } |
323 | "#, | 301 | "#, |
324 | &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], | 302 | &["{unknown}"], |
325 | ); | 303 | ); |
326 | } | 304 | } |
327 | 305 | ||
@@ -411,6 +389,23 @@ mod tests { | |||
411 | } | 389 | } |
412 | 390 | ||
413 | #[test] | 391 | #[test] |
392 | fn hover_omits_default_generic_types() { | ||
393 | check_hover_result( | ||
394 | r#" | ||
395 | //- /main.rs | ||
396 | struct Test<K, T = u8> { | ||
397 | k: K, | ||
398 | t: T, | ||
399 | } | ||
400 | |||
401 | fn main() { | ||
402 | let zz<|> = Test { t: 23, k: 33 }; | ||
403 | }"#, | ||
404 | &["Test<i32>"], | ||
405 | ); | ||
406 | } | ||
407 | |||
408 | #[test] | ||
414 | fn hover_some() { | 409 | fn hover_some() { |
415 | let (analysis, position) = single_file_with_position( | 410 | let (analysis, position) = single_file_with_position( |
416 | " | 411 | " |
@@ -505,6 +500,13 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
505 | } | 500 | } |
506 | 501 | ||
507 | #[test] | 502 | #[test] |
503 | fn hover_for_param_edge() { | ||
504 | let (analysis, position) = single_file_with_position("fn func(<|>foo: i32) {}"); | ||
505 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
506 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
507 | } | ||
508 | |||
509 | #[test] | ||
508 | fn test_type_of_for_function() { | 510 | fn test_type_of_for_function() { |
509 | let (analysis, range) = single_file_with_range( | 511 | let (analysis, range) = single_file_with_range( |
510 | " | 512 | " |