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.rs730
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
3use hir::{db::AstDatabase, Adt, HasSource, HirDisplay};
4use ra_db::SourceDatabase;
5use ra_syntax::{
6 algo::find_covering_element,
7 ast::{self, DocCommentsOwner},
8 match_ast, AstNode,
9};
10
11use 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)]
24pub struct HoverResult {
25 results: Vec<String>,
26 exact: bool,
27}
28
29impl Default for HoverResult {
30 fn default() -> Self {
31 HoverResult::new()
32 }
33}
34
35impl 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
88fn 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
96fn 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
157pub(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
221pub(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)]
243mod 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 &["
453None
454```
455
456The 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 &["
473Some
474```
475
476The 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 "
500fn 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}