diff options
author | Seivan Heidari <[email protected]> | 2019-11-28 07:19:14 +0000 |
---|---|---|
committer | Seivan Heidari <[email protected]> | 2019-11-28 07:19:14 +0000 |
commit | 18a0937585b836ec5ed054b9ae48e0156ab6d9ef (patch) | |
tree | 9de2c0267ddcc00df717f90034d0843d751a851b /crates/ra_ide/src/hover.rs | |
parent | a7394b44c870f585eacfeb3036a33471aff49ff8 (diff) | |
parent | 484acc8a61d599662ed63a4cbda091d38a982551 (diff) |
Merge branch 'master' of https://github.com/rust-analyzer/rust-analyzer into feature/themes
Diffstat (limited to 'crates/ra_ide/src/hover.rs')
-rw-r--r-- | crates/ra_ide/src/hover.rs | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs new file mode 100644 index 000000000..260a7b869 --- /dev/null +++ b/crates/ra_ide/src/hover.rs | |||
@@ -0,0 +1,730 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{db::AstDatabase, Adt, HasSource, HirDisplay}; | ||
4 | use ra_db::SourceDatabase; | ||
5 | use ra_syntax::{ | ||
6 | algo::find_covering_element, | ||
7 | ast::{self, DocCommentsOwner}, | ||
8 | match_ast, AstNode, | ||
9 | }; | ||
10 | |||
11 | use crate::{ | ||
12 | db::RootDatabase, | ||
13 | display::{ | ||
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, | ||
18 | references::{classify_name, classify_name_ref, NameKind, NameKind::*}, | ||
19 | FilePosition, FileRange, RangeInfo, | ||
20 | }; | ||
21 | |||
22 | /// Contains the results when hovering over an item | ||
23 | #[derive(Debug, Clone)] | ||
24 | pub struct HoverResult { | ||
25 | results: Vec<String>, | ||
26 | exact: bool, | ||
27 | } | ||
28 | |||
29 | impl Default for HoverResult { | ||
30 | fn default() -> Self { | ||
31 | HoverResult::new() | ||
32 | } | ||
33 | } | ||
34 | |||
35 | impl HoverResult { | ||
36 | pub fn new() -> HoverResult { | ||
37 | HoverResult { | ||
38 | results: Vec::new(), | ||
39 | // We assume exact by default | ||
40 | exact: true, | ||
41 | } | ||
42 | } | ||
43 | |||
44 | pub fn extend(&mut self, item: Option<String>) { | ||
45 | self.results.extend(item); | ||
46 | } | ||
47 | |||
48 | pub fn is_exact(&self) -> bool { | ||
49 | self.exact | ||
50 | } | ||
51 | |||
52 | pub fn is_empty(&self) -> bool { | ||
53 | self.results.is_empty() | ||
54 | } | ||
55 | |||
56 | pub fn len(&self) -> usize { | ||
57 | self.results.len() | ||
58 | } | ||
59 | |||
60 | pub fn first(&self) -> Option<&str> { | ||
61 | self.results.first().map(String::as_str) | ||
62 | } | ||
63 | |||
64 | pub fn results(&self) -> &[String] { | ||
65 | &self.results | ||
66 | } | ||
67 | |||
68 | /// Returns the results converted into markup | ||
69 | /// for displaying in a UI | ||
70 | pub fn to_markup(&self) -> String { | ||
71 | let mut markup = if !self.exact { | ||
72 | let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits."); | ||
73 | if !self.results.is_empty() { | ||
74 | msg.push_str(" \nThese items were found instead:"); | ||
75 | } | ||
76 | msg.push_str("\n\n---\n"); | ||
77 | msg | ||
78 | } else { | ||
79 | String::new() | ||
80 | }; | ||
81 | |||
82 | markup.push_str(&self.results.join("\n\n---\n")); | ||
83 | |||
84 | markup | ||
85 | } | ||
86 | } | ||
87 | |||
88 | fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> { | ||
89 | match (desc, docs) { | ||
90 | (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)), | ||
91 | (None, Some(docs)) => Some(docs), | ||
92 | _ => None, | ||
93 | } | ||
94 | } | ||
95 | |||
96 | fn hover_text_from_name_kind( | ||
97 | db: &RootDatabase, | ||
98 | name_kind: NameKind, | ||
99 | no_fallback: &mut bool, | ||
100 | ) -> Option<String> { | ||
101 | return match name_kind { | ||
102 | Macro(it) => { | ||
103 | let src = it.source(db); | ||
104 | hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value))) | ||
105 | } | ||
106 | Field(it) => { | ||
107 | let src = it.source(db); | ||
108 | match src.value { | ||
109 | hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()), | ||
110 | _ => None, | ||
111 | } | ||
112 | } | ||
113 | AssocItem(it) => match it { | ||
114 | hir::AssocItem::Function(it) => from_def_source(db, it), | ||
115 | hir::AssocItem::Const(it) => from_def_source(db, it), | ||
116 | hir::AssocItem::TypeAlias(it) => from_def_source(db, it), | ||
117 | }, | ||
118 | Def(it) => match it { | ||
119 | hir::ModuleDef::Module(it) => match it.definition_source(db).value { | ||
120 | hir::ModuleSource::Module(it) => { | ||
121 | hover_text(it.doc_comment_text(), it.short_label()) | ||
122 | } | ||
123 | _ => None, | ||
124 | }, | ||
125 | hir::ModuleDef::Function(it) => from_def_source(db, it), | ||
126 | hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it), | ||
127 | hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it), | ||
128 | hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it), | ||
129 | hir::ModuleDef::EnumVariant(it) => from_def_source(db, it), | ||
130 | hir::ModuleDef::Const(it) => from_def_source(db, it), | ||
131 | hir::ModuleDef::Static(it) => from_def_source(db, it), | ||
132 | hir::ModuleDef::Trait(it) => from_def_source(db, it), | ||
133 | hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), | ||
134 | hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), | ||
135 | }, | ||
136 | Local(_) => { | ||
137 | // Hover for these shows type names | ||
138 | *no_fallback = true; | ||
139 | None | ||
140 | } | ||
141 | GenericParam(_) | SelfType(_) => { | ||
142 | // FIXME: Hover for generic param | ||
143 | None | ||
144 | } | ||
145 | }; | ||
146 | |||
147 | fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String> | ||
148 | where | ||
149 | D: HasSource<Ast = A>, | ||
150 | A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, | ||
151 | { | ||
152 | let src = def.source(db); | ||
153 | hover_text(src.value.doc_comment_text(), src.value.short_label()) | ||
154 | } | ||
155 | } | ||
156 | |||
157 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
158 | 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()?; | ||
160 | let token = descend_into_macros(db, position.file_id, token); | ||
161 | |||
162 | let mut res = HoverResult::new(); | ||
163 | |||
164 | let mut range = match_ast! { | ||
165 | match (token.value.parent()) { | ||
166 | ast::NameRef(name_ref) => { | ||
167 | let mut no_fallback = false; | ||
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 | }, | ||
190 | ast::Name(name) => { | ||
191 | if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| 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 | }, | ||
201 | _ => None, | ||
202 | } | ||
203 | }; | ||
204 | |||
205 | if range.is_none() { | ||
206 | let node = token.value.ancestors().find(|n| { | ||
207 | ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some() | ||
208 | })?; | ||
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 | |||
214 | let range = range?; | ||
215 | if res.is_empty() { | ||
216 | return None; | ||
217 | } | ||
218 | Some(RangeInfo::new(range, res)) | ||
219 | } | ||
220 | |||
221 | pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> { | ||
222 | let parse = db.parse(frange.file_id); | ||
223 | let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); | ||
224 | // if we picked identifier, expand to pattern/expression | ||
225 | let node = leaf_node | ||
226 | .ancestors() | ||
227 | .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())?; | ||
229 | let analyzer = | ||
230 | hir::SourceAnalyzer::new(db, hir::Source::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)) | ||
232 | { | ||
233 | ty | ||
234 | } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) { | ||
235 | ty | ||
236 | } else { | ||
237 | return None; | ||
238 | }; | ||
239 | Some(ty.display(db).to_string()) | ||
240 | } | ||
241 | |||
242 | #[cfg(test)] | ||
243 | mod tests { | ||
244 | use crate::mock_analysis::{ | ||
245 | analysis_and_position, single_file_with_position, single_file_with_range, | ||
246 | }; | ||
247 | use ra_syntax::TextRange; | ||
248 | |||
249 | fn trim_markup(s: &str) -> &str { | ||
250 | s.trim_start_matches("```rust\n").trim_end_matches("\n```") | ||
251 | } | ||
252 | |||
253 | fn trim_markup_opt(s: Option<&str>) -> Option<&str> { | ||
254 | s.map(trim_markup) | ||
255 | } | ||
256 | |||
257 | fn check_hover_result(fixture: &str, expected: &[&str]) { | ||
258 | let (analysis, position) = analysis_and_position(fixture); | ||
259 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
260 | let mut results = Vec::from(hover.info.results()); | ||
261 | results.sort(); | ||
262 | |||
263 | for (markup, expected) in | ||
264 | results.iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>"))) | ||
265 | { | ||
266 | assert_eq!(trim_markup(&markup), *expected); | ||
267 | } | ||
268 | |||
269 | assert_eq!(hover.info.len(), expected.len()); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn hover_shows_type_of_an_expression() { | ||
274 | let (analysis, position) = single_file_with_position( | ||
275 | " | ||
276 | pub fn foo() -> u32 { 1 } | ||
277 | |||
278 | fn main() { | ||
279 | let foo_test = foo()<|>; | ||
280 | } | ||
281 | ", | ||
282 | ); | ||
283 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
284 | assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); | ||
285 | assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn hover_shows_fn_signature() { | ||
290 | // Single file with result | ||
291 | check_hover_result( | ||
292 | r#" | ||
293 | //- /main.rs | ||
294 | pub fn foo() -> u32 { 1 } | ||
295 | |||
296 | fn main() { | ||
297 | let foo_test = fo<|>o(); | ||
298 | } | ||
299 | "#, | ||
300 | &["pub fn foo() -> u32"], | ||
301 | ); | ||
302 | |||
303 | // Multiple results | ||
304 | check_hover_result( | ||
305 | r#" | ||
306 | //- /a.rs | ||
307 | pub fn foo() -> u32 { 1 } | ||
308 | |||
309 | //- /b.rs | ||
310 | pub fn foo() -> &str { "" } | ||
311 | |||
312 | //- /c.rs | ||
313 | pub fn foo(a: u32, b: u32) {} | ||
314 | |||
315 | //- /main.rs | ||
316 | mod a; | ||
317 | mod b; | ||
318 | mod c; | ||
319 | |||
320 | fn main() { | ||
321 | let foo_test = fo<|>o(); | ||
322 | } | ||
323 | "#, | ||
324 | &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn hover_shows_fn_signature_with_type_params() { | ||
330 | check_hover_result( | ||
331 | r#" | ||
332 | //- /main.rs | ||
333 | pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { } | ||
334 | |||
335 | fn main() { | ||
336 | let foo_test = fo<|>o(); | ||
337 | } | ||
338 | "#, | ||
339 | &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"], | ||
340 | ); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn hover_shows_fn_signature_on_fn_name() { | ||
345 | check_hover_result( | ||
346 | r#" | ||
347 | //- /main.rs | ||
348 | pub fn foo<|>(a: u32, b: u32) -> u32 {} | ||
349 | |||
350 | fn main() { | ||
351 | } | ||
352 | "#, | ||
353 | &["pub fn foo(a: u32, b: u32) -> u32"], | ||
354 | ); | ||
355 | } | ||
356 | |||
357 | #[test] | ||
358 | fn hover_shows_struct_field_info() { | ||
359 | // Hovering over the field when instantiating | ||
360 | check_hover_result( | ||
361 | r#" | ||
362 | //- /main.rs | ||
363 | struct Foo { | ||
364 | field_a: u32, | ||
365 | } | ||
366 | |||
367 | fn main() { | ||
368 | let foo = Foo { | ||
369 | field_a<|>: 0, | ||
370 | }; | ||
371 | } | ||
372 | "#, | ||
373 | &["field_a: u32"], | ||
374 | ); | ||
375 | |||
376 | // Hovering over the field in the definition | ||
377 | check_hover_result( | ||
378 | r#" | ||
379 | //- /main.rs | ||
380 | struct Foo { | ||
381 | field_a<|>: u32, | ||
382 | } | ||
383 | |||
384 | fn main() { | ||
385 | let foo = Foo { | ||
386 | field_a: 0, | ||
387 | }; | ||
388 | } | ||
389 | "#, | ||
390 | &["field_a: u32"], | ||
391 | ); | ||
392 | } | ||
393 | |||
394 | #[test] | ||
395 | fn hover_const_static() { | ||
396 | check_hover_result( | ||
397 | r#" | ||
398 | //- /main.rs | ||
399 | const foo<|>: u32 = 0; | ||
400 | "#, | ||
401 | &["const foo: u32"], | ||
402 | ); | ||
403 | |||
404 | check_hover_result( | ||
405 | r#" | ||
406 | //- /main.rs | ||
407 | static foo<|>: u32 = 0; | ||
408 | "#, | ||
409 | &["static foo: u32"], | ||
410 | ); | ||
411 | } | ||
412 | |||
413 | #[test] | ||
414 | fn hover_some() { | ||
415 | let (analysis, position) = single_file_with_position( | ||
416 | " | ||
417 | enum Option<T> { Some(T) } | ||
418 | use Option::Some; | ||
419 | |||
420 | fn main() { | ||
421 | So<|>me(12); | ||
422 | } | ||
423 | ", | ||
424 | ); | ||
425 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
426 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Some")); | ||
427 | |||
428 | let (analysis, position) = single_file_with_position( | ||
429 | " | ||
430 | enum Option<T> { Some(T) } | ||
431 | use Option::Some; | ||
432 | |||
433 | fn main() { | ||
434 | let b<|>ar = Some(12); | ||
435 | } | ||
436 | ", | ||
437 | ); | ||
438 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
439 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Option<i32>")); | ||
440 | } | ||
441 | |||
442 | #[test] | ||
443 | fn hover_enum_variant() { | ||
444 | check_hover_result( | ||
445 | r#" | ||
446 | //- /main.rs | ||
447 | enum Option<T> { | ||
448 | /// The None variant | ||
449 | Non<|>e | ||
450 | } | ||
451 | "#, | ||
452 | &[" | ||
453 | None | ||
454 | ``` | ||
455 | |||
456 | The None variant | ||
457 | " | ||
458 | .trim()], | ||
459 | ); | ||
460 | |||
461 | check_hover_result( | ||
462 | r#" | ||
463 | //- /main.rs | ||
464 | enum Option<T> { | ||
465 | /// The Some variant | ||
466 | Some(T) | ||
467 | } | ||
468 | fn main() { | ||
469 | let s = Option::Som<|>e(12); | ||
470 | } | ||
471 | "#, | ||
472 | &[" | ||
473 | Some | ||
474 | ``` | ||
475 | |||
476 | The Some variant | ||
477 | " | ||
478 | .trim()], | ||
479 | ); | ||
480 | } | ||
481 | |||
482 | #[test] | ||
483 | fn hover_for_local_variable() { | ||
484 | let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); | ||
485 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
486 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
487 | } | ||
488 | |||
489 | #[test] | ||
490 | fn hover_for_local_variable_pat() { | ||
491 | let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); | ||
492 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
493 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
494 | } | ||
495 | |||
496 | #[test] | ||
497 | fn hover_local_var_edge() { | ||
498 | let (analysis, position) = single_file_with_position( | ||
499 | " | ||
500 | fn func(foo: i32) { if true { <|>foo; }; } | ||
501 | ", | ||
502 | ); | ||
503 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
504 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
505 | } | ||
506 | |||
507 | #[test] | ||
508 | fn test_type_of_for_function() { | ||
509 | let (analysis, range) = single_file_with_range( | ||
510 | " | ||
511 | pub fn foo() -> u32 { 1 }; | ||
512 | |||
513 | fn main() { | ||
514 | let foo_test = <|>foo()<|>; | ||
515 | } | ||
516 | ", | ||
517 | ); | ||
518 | |||
519 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
520 | assert_eq!("u32", &type_name); | ||
521 | } | ||
522 | |||
523 | #[test] | ||
524 | fn test_type_of_for_expr() { | ||
525 | let (analysis, range) = single_file_with_range( | ||
526 | " | ||
527 | fn main() { | ||
528 | let foo: usize = 1; | ||
529 | let bar = <|>1 + foo<|>; | ||
530 | } | ||
531 | ", | ||
532 | ); | ||
533 | |||
534 | let type_name = analysis.type_of(range).unwrap().unwrap(); | ||
535 | assert_eq!("usize", &type_name); | ||
536 | } | ||
537 | |||
538 | #[test] | ||
539 | fn test_hover_infer_associated_method_result() { | ||
540 | let (analysis, position) = single_file_with_position( | ||
541 | " | ||
542 | struct Thing { x: u32 } | ||
543 | |||
544 | impl Thing { | ||
545 | fn new() -> Thing { | ||
546 | Thing { x: 0 } | ||
547 | } | ||
548 | } | ||
549 | |||
550 | fn main() { | ||
551 | let foo_<|>test = Thing::new(); | ||
552 | } | ||
553 | ", | ||
554 | ); | ||
555 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
556 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); | ||
557 | } | ||
558 | |||
559 | #[test] | ||
560 | fn test_hover_infer_associated_method_exact() { | ||
561 | let (analysis, position) = single_file_with_position( | ||
562 | " | ||
563 | struct Thing { x: u32 } | ||
564 | |||
565 | impl Thing { | ||
566 | fn new() -> Thing { | ||
567 | Thing { x: 0 } | ||
568 | } | ||
569 | } | ||
570 | |||
571 | fn main() { | ||
572 | let foo_test = Thing::new<|>(); | ||
573 | } | ||
574 | ", | ||
575 | ); | ||
576 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
577 | assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing")); | ||
578 | assert_eq!(hover.info.is_exact(), true); | ||
579 | } | ||
580 | |||
581 | #[test] | ||
582 | fn test_hover_infer_associated_const_in_pattern() { | ||
583 | let (analysis, position) = single_file_with_position( | ||
584 | " | ||
585 | struct X; | ||
586 | impl X { | ||
587 | const C: u32 = 1; | ||
588 | } | ||
589 | |||
590 | fn main() { | ||
591 | match 1 { | ||
592 | X::C<|> => {}, | ||
593 | 2 => {}, | ||
594 | _ => {} | ||
595 | }; | ||
596 | } | ||
597 | ", | ||
598 | ); | ||
599 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
600 | assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); | ||
601 | assert_eq!(hover.info.is_exact(), true); | ||
602 | } | ||
603 | |||
604 | #[test] | ||
605 | fn test_hover_self() { | ||
606 | let (analysis, position) = single_file_with_position( | ||
607 | " | ||
608 | struct Thing { x: u32 } | ||
609 | impl Thing { | ||
610 | fn new() -> Self { | ||
611 | Self<|> { x: 0 } | ||
612 | } | ||
613 | } | ||
614 | ", | ||
615 | ); | ||
616 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
617 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); | ||
618 | assert_eq!(hover.info.is_exact(), true); | ||
619 | |||
620 | /* FIXME: revive these tests | ||
621 | let (analysis, position) = single_file_with_position( | ||
622 | " | ||
623 | struct Thing { x: u32 } | ||
624 | impl Thing { | ||
625 | fn new() -> Self<|> { | ||
626 | Self { x: 0 } | ||
627 | } | ||
628 | } | ||
629 | ", | ||
630 | ); | ||
631 | |||
632 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
633 | assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); | ||
634 | assert_eq!(hover.info.is_exact(), true); | ||
635 | |||
636 | let (analysis, position) = single_file_with_position( | ||
637 | " | ||
638 | enum Thing { A } | ||
639 | impl Thing { | ||
640 | pub fn new() -> Self<|> { | ||
641 | Thing::A | ||
642 | } | ||
643 | } | ||
644 | ", | ||
645 | ); | ||
646 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
647 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); | ||
648 | assert_eq!(hover.info.is_exact(), true); | ||
649 | |||
650 | let (analysis, position) = single_file_with_position( | ||
651 | " | ||
652 | enum Thing { A } | ||
653 | impl Thing { | ||
654 | pub fn thing(a: Self<|>) { | ||
655 | } | ||
656 | } | ||
657 | ", | ||
658 | ); | ||
659 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
660 | assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); | ||
661 | assert_eq!(hover.info.is_exact(), true); | ||
662 | */ | ||
663 | } | ||
664 | |||
665 | #[test] | ||
666 | fn test_hover_shadowing_pat() { | ||
667 | let (analysis, position) = single_file_with_position( | ||
668 | " | ||
669 | fn x() {} | ||
670 | |||
671 | fn y() { | ||
672 | let x = 0i32; | ||
673 | x<|>; | ||
674 | } | ||
675 | ", | ||
676 | ); | ||
677 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
678 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
679 | assert_eq!(hover.info.is_exact(), true); | ||
680 | } | ||
681 | |||
682 | #[test] | ||
683 | fn test_hover_macro_invocation() { | ||
684 | let (analysis, position) = single_file_with_position( | ||
685 | " | ||
686 | macro_rules! foo { | ||
687 | () => {} | ||
688 | } | ||
689 | |||
690 | fn f() { | ||
691 | fo<|>o!(); | ||
692 | } | ||
693 | ", | ||
694 | ); | ||
695 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
696 | assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); | ||
697 | assert_eq!(hover.info.is_exact(), true); | ||
698 | } | ||
699 | |||
700 | #[test] | ||
701 | fn test_hover_tuple_field() { | ||
702 | let (analysis, position) = single_file_with_position( | ||
703 | " | ||
704 | struct TS(String, i32<|>); | ||
705 | ", | ||
706 | ); | ||
707 | let hover = analysis.hover(position).unwrap().unwrap(); | ||
708 | assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); | ||
709 | assert_eq!(hover.info.is_exact(), true); | ||
710 | } | ||
711 | |||
712 | #[test] | ||
713 | fn test_hover_through_macro() { | ||
714 | check_hover_result( | ||
715 | " | ||
716 | //- /lib.rs | ||
717 | macro_rules! id { | ||
718 | ($($tt:tt)*) => { $($tt)* } | ||
719 | } | ||
720 | fn foo() {} | ||
721 | id! { | ||
722 | fn bar() { | ||
723 | fo<|>o(); | ||
724 | } | ||
725 | } | ||
726 | ", | ||
727 | &["fn foo()"], | ||
728 | ); | ||
729 | } | ||
730 | } | ||