1 //! FIXME: write short doc here
3 use hir::{Adt, BuiltinType, HasSource, HirDisplay};
4 use ra_db::SourceDatabase;
6 algo::{ancestors_at_offset, find_covering_element, find_node_at_offset},
7 ast::{self, DocCommentsOwner},
14 description_from_symbol, docs_from_symbol, macro_label, rust_code_markup,
15 rust_code_markup_with_doc, ShortLabel,
17 references::{classify_name_ref, NameKind::*},
18 FilePosition, FileRange, RangeInfo,
21 /// Contains the results when hovering over an item
22 #[derive(Debug, Clone)]
23 pub struct HoverResult {
28 impl Default for HoverResult {
29 fn default() -> Self {
35 pub fn new() -> HoverResult {
38 // We assume exact by default
43 pub fn extend(&mut self, item: Option<String>) {
44 self.results.extend(item);
47 pub fn is_exact(&self) -> bool {
51 pub fn is_empty(&self) -> bool {
52 self.results.is_empty()
55 pub fn len(&self) -> usize {
59 pub fn first(&self) -> Option<&str> {
60 self.results.first().map(String::as_str)
63 pub fn results(&self) -> &[String] {
67 /// Returns the results converted into markup
68 /// for displaying in a UI
69 pub fn to_markup(&self) -> String {
70 let mut markup = if !self.exact {
71 let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits.");
72 if !self.results.is_empty() {
73 msg.push_str(" \nThese items were found instead:");
75 msg.push_str("\n\n---\n");
81 markup.push_str(&self.results.join("\n\n---\n"));
87 fn hover_text(docs: Option<String>, desc: Option<String>) -> Option<String> {
89 (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)),
90 (None, Some(docs)) => Some(docs),
95 pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> {
96 let parse = db.parse(position.file_id);
97 let file = parse.tree();
98 let mut res = HoverResult::new();
100 let mut range = None;
101 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), position.offset) {
102 let mut no_fallback = false;
103 let name_kind = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind);
106 let src = it.source(db);
107 res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast))));
110 let src = it.source(db);
111 if let hir::FieldSource::Named(it) = src.ast {
112 res.extend(hover_text(it.doc_comment_text(), it.short_label()));
115 Some(AssocItem(it)) => res.extend(match it {
116 hir::AssocItem::Function(it) => from_def_source(db, it),
117 hir::AssocItem::Const(it) => from_def_source(db, it),
118 hir::AssocItem::TypeAlias(it) => from_def_source(db, it),
120 Some(Def(it)) => match it {
121 hir::ModuleDef::Module(it) => {
122 if let hir::ModuleSource::Module(it) = it.definition_source(db).ast {
123 res.extend(hover_text(it.doc_comment_text(), it.short_label()))
126 hir::ModuleDef::Function(it) => res.extend(from_def_source(db, it)),
127 hir::ModuleDef::Adt(Adt::Struct(it)) => res.extend(from_def_source(db, it)),
128 hir::ModuleDef::Adt(Adt::Union(it)) => res.extend(from_def_source(db, it)),
129 hir::ModuleDef::Adt(Adt::Enum(it)) => res.extend(from_def_source(db, it)),
130 hir::ModuleDef::EnumVariant(it) => res.extend(from_def_source(db, it)),
131 hir::ModuleDef::Const(it) => res.extend(from_def_source(db, it)),
132 hir::ModuleDef::Static(it) => res.extend(from_def_source(db, it)),
133 hir::ModuleDef::Trait(it) => res.extend(from_def_source(db, it)),
134 hir::ModuleDef::TypeAlias(it) => res.extend(from_def_source(db, it)),
135 hir::ModuleDef::BuiltinType(it) => {
136 if let Some(b) = BuiltinType::ALL.iter().find(|(_, ty)| *ty == it) {
137 res.extend(Some(b.0.to_string()))
141 Some(SelfType(ty)) => {
142 if let Some((adt_def, _)) = ty.as_adt() {
143 res.extend(match adt_def {
144 hir::Adt::Struct(it) => from_def_source(db, it),
145 hir::Adt::Union(it) => from_def_source(db, it),
146 hir::Adt::Enum(it) => from_def_source(db, it),
150 Some(Pat(_)) | Some(SelfParam(_)) => {
151 // Hover for these shows type names
154 Some(GenericParam(_)) => {
155 // FIXME: Hover for generic param
160 if res.is_empty() && !no_fallback {
161 // Fallback index based approach:
162 let symbols = crate::symbol_index::index_resolve(db, &name_ref);
164 let docs = docs_from_symbol(db, &sym);
165 let desc = description_from_symbol(db, &sym);
166 res.extend(hover_text(docs, desc));
171 range = Some(name_ref.syntax().text_range())
173 } else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
174 if let Some(parent) = name.syntax().parent() {
175 let text = match_ast! {
177 ast::StructDef(it) => {
178 hover_text(it.doc_comment_text(), it.short_label())
180 ast::EnumDef(it) => {
181 hover_text(it.doc_comment_text(), it.short_label())
183 ast::EnumVariant(it) => {
184 hover_text(it.doc_comment_text(), it.short_label())
187 hover_text(it.doc_comment_text(), it.short_label())
189 ast::TypeAliasDef(it) => {
190 hover_text(it.doc_comment_text(), it.short_label())
192 ast::ConstDef(it) => {
193 hover_text(it.doc_comment_text(), it.short_label())
195 ast::StaticDef(it) => {
196 hover_text(it.doc_comment_text(), it.short_label())
198 ast::TraitDef(it) => {
199 hover_text(it.doc_comment_text(), it.short_label())
201 ast::RecordFieldDef(it) => {
202 hover_text(it.doc_comment_text(), it.short_label())
205 hover_text(it.doc_comment_text(), it.short_label())
207 ast::MacroCall(it) => {
208 hover_text(it.doc_comment_text(), None)
216 if !res.is_empty() && range.is_none() {
217 range = Some(name.syntax().text_range());
222 let node = ancestors_at_offset(file.syntax(), position.offset).find(|n| {
223 ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()
225 let frange = FileRange { file_id: position.file_id, range: node.text_range() };
226 res.extend(type_of(db, frange).map(rust_code_markup));
227 range = Some(node.text_range());
234 let res = RangeInfo::new(range, res);
237 fn from_def_source<A, D>(db: &RootDatabase, def: D) -> Option<String>
239 D: HasSource<Ast = A>,
240 A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel,
242 let src = def.source(db);
243 hover_text(src.ast.doc_comment_text(), src.ast.short_label())
247 pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option<String> {
248 let parse = db.parse(frange.file_id);
249 let leaf_node = find_covering_element(parse.tree().syntax(), frange.range);
250 // if we picked identifier, expand to pattern/expression
253 .take_while(|it| it.text_range() == leaf_node.text_range())
254 .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?;
255 let analyzer = hir::SourceAnalyzer::new(db, frange.file_id, &node, None);
256 let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e))
259 } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) {
264 Some(ty.display(db).to_string())
269 use crate::mock_analysis::{
270 analysis_and_position, single_file_with_position, single_file_with_range,
272 use ra_syntax::TextRange;
274 fn trim_markup(s: &str) -> &str {
275 s.trim_start_matches("```rust\n").trim_end_matches("\n```")
278 fn trim_markup_opt(s: Option<&str>) -> Option<&str> {
282 fn check_hover_result(fixture: &str, expected: &[&str]) {
283 let (analysis, position) = analysis_and_position(fixture);
284 let hover = analysis.hover(position).unwrap().unwrap();
285 let mut results = Vec::from(hover.info.results());
288 for (markup, expected) in
289 results.iter().zip(expected.iter().chain(std::iter::repeat(&"<missing>")))
291 assert_eq!(trim_markup(&markup), *expected);
294 assert_eq!(hover.info.len(), expected.len());
298 fn hover_shows_type_of_an_expression() {
299 let (analysis, position) = single_file_with_position(
301 pub fn foo() -> u32 { 1 }
304 let foo_test = foo()<|>;
308 let hover = analysis.hover(position).unwrap().unwrap();
309 assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into()));
310 assert_eq!(trim_markup_opt(hover.info.first()), Some("u32"));
314 fn hover_shows_fn_signature() {
315 // Single file with result
319 pub fn foo() -> u32 { 1 }
322 let foo_test = fo<|>o();
325 &["pub fn foo() -> u32"],
332 pub fn foo() -> u32 { 1 }
335 pub fn foo() -> &str { "" }
338 pub fn foo(a: u32, b: u32) {}
346 let foo_test = fo<|>o();
349 &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"],
354 fn hover_shows_fn_signature_with_type_params() {
358 pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str { }
361 let foo_test = fo<|>o();
364 &["pub fn foo<'a, T: AsRef<str>>(b: &'a T) -> &'a str"],
369 fn hover_shows_fn_signature_on_fn_name() {
373 pub fn foo<|>(a: u32, b: u32) -> u32 {}
378 &["pub fn foo(a: u32, b: u32) -> u32"],
383 fn hover_shows_struct_field_info() {
384 // Hovering over the field when instantiating
401 // Hovering over the field in the definition
420 fn hover_const_static() {
425 const foo<|>: u32 = 0;
435 static foo<|>: u32 = 0;
438 &["static foo: u32"],
444 let (analysis, position) = single_file_with_position(
446 enum Option<T> { Some(T) }
454 let hover = analysis.hover(position).unwrap().unwrap();
455 assert_eq!(trim_markup_opt(hover.info.first()), Some("Some"));
457 let (analysis, position) = single_file_with_position(
459 enum Option<T> { Some(T) }
463 let b<|>ar = Some(12);
467 let hover = analysis.hover(position).unwrap().unwrap();
468 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option<i32>"));
472 fn hover_enum_variant() {
498 let s = Option::Som<|>e(12);
512 fn hover_for_local_variable() {
513 let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }");
514 let hover = analysis.hover(position).unwrap().unwrap();
515 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
519 fn hover_for_local_variable_pat() {
520 let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}");
521 let hover = analysis.hover(position).unwrap().unwrap();
522 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
526 fn hover_local_var_edge() {
527 let (analysis, position) = single_file_with_position(
529 fn func(foo: i32) { if true { <|>foo; }; }
532 let hover = analysis.hover(position).unwrap().unwrap();
533 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
537 fn test_type_of_for_function() {
538 let (analysis, range) = single_file_with_range(
540 pub fn foo() -> u32 { 1 };
543 let foo_test = <|>foo()<|>;
548 let type_name = analysis.type_of(range).unwrap().unwrap();
549 assert_eq!("u32", &type_name);
553 fn test_type_of_for_expr() {
554 let (analysis, range) = single_file_with_range(
558 let bar = <|>1 + foo<|>;
563 let type_name = analysis.type_of(range).unwrap().unwrap();
564 assert_eq!("usize", &type_name);
568 fn test_hover_infer_associated_method_result() {
569 let (analysis, position) = single_file_with_position(
571 struct Thing { x: u32 }
580 let foo_<|>test = Thing::new();
584 let hover = analysis.hover(position).unwrap().unwrap();
585 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
589 fn test_hover_infer_associated_method_exact() {
590 let (analysis, position) = single_file_with_position(
592 struct Thing { x: u32 }
601 let foo_test = Thing::new<|>();
605 let hover = analysis.hover(position).unwrap().unwrap();
606 assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing"));
607 assert_eq!(hover.info.is_exact(), true);
611 fn test_hover_infer_associated_const_in_pattern() {
612 let (analysis, position) = single_file_with_position(
628 let hover = analysis.hover(position).unwrap().unwrap();
629 assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32"));
630 assert_eq!(hover.info.is_exact(), true);
634 fn test_hover_self() {
635 let (analysis, position) = single_file_with_position(
637 struct Thing { x: u32 }
645 let hover = analysis.hover(position).unwrap().unwrap();
646 assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing"));
647 assert_eq!(hover.info.is_exact(), true);
649 let (analysis, position) = single_file_with_position(
651 struct Thing { x: u32 }
653 fn new() -> Self<|> {
659 let hover = analysis.hover(position).unwrap().unwrap();
660 assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing"));
661 assert_eq!(hover.info.is_exact(), true);
663 let (analysis, position) = single_file_with_position(
667 pub fn new() -> Self<|> {
673 let hover = analysis.hover(position).unwrap().unwrap();
674 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
675 assert_eq!(hover.info.is_exact(), true);
677 let (analysis, position) = single_file_with_position(
681 pub fn thing(a: Self<|>) {
686 let hover = analysis.hover(position).unwrap().unwrap();
687 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
688 assert_eq!(hover.info.is_exact(), true);
692 fn test_hover_shadowing_pat() {
693 let (analysis, position) = single_file_with_position(
703 let hover = analysis.hover(position).unwrap().unwrap();
704 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
705 assert_eq!(hover.info.is_exact(), true);
709 fn test_hover_macro_invocation() {
710 let (analysis, position) = single_file_with_position(
721 let hover = analysis.hover(position).unwrap().unwrap();
722 assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo"));
723 assert_eq!(hover.info.is_exact(), true);
727 fn test_hover_tuple_field() {
728 let (analysis, position) = single_file_with_position(
730 struct TS(String, i32<|>);
733 let hover = analysis.hover(position).unwrap().unwrap();
734 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
735 assert_eq!(hover.info.is_exact(), true);