diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 8b6c0bedee7..2eeba057299 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -87,12 +87,18 @@ impl Attrs { } pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { + let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( + |docs_text| Attr { + input: Some(AttrInput::Literal(SmolStr::new(docs_text))), + path: ModPath::from(hir_expand::name!(doc)), + }, + ); let mut attrs = owner.attrs().peekable(); let entries = if attrs.peek().is_none() { // Avoid heap allocation None } else { - Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) }; Attrs { entries } } diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1cece..2630b3d895e 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -29,6 +29,13 @@ impl Documentation { Documentation(s.into()) } + pub fn from_ast(node: &N) -> Option + where + N: ast::DocCommentsOwner + ast::AttrsOwner, + { + docs_from_ast(node) + } + pub fn as_str(&self) -> &str { &*self.0 } @@ -70,6 +77,45 @@ impl Documentation { } } -pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - node.doc_comment_text().map(|it| Documentation::new(&it)) +pub(crate) fn docs_from_ast(node: &N) -> Option +where + N: ast::DocCommentsOwner + ast::AttrsOwner, +{ + let doc_comment_text = node.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(node); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + docs.map(|it| Documentation::new(&it)) +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs.trim_end_matches("\n\n").to_owned()) + } } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index ea495cb11a2..660bdfe3365 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -153,6 +153,7 @@ pub mod known { str, // Special names macro_rules, + doc, // Components of known path (value or mod name) std, core, diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index d890b69d26f..a721e23c693 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -125,3 +125,81 @@ pub(crate) fn completions( Some(acc) } + +#[cfg(test)] +mod tests { + use crate::completion::completion_config::CompletionConfig; + use crate::mock_analysis::analysis_and_position; + + struct DetailAndDocumentation<'a> { + detail: &'a str, + documentation: &'a str, + } + + fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { + let (analysis, position) = analysis_and_position(fixture); + let config = CompletionConfig::default(); + let completions = analysis.completions(&config, position).unwrap().unwrap(); + for item in completions { + if item.detail() == Some(expected.detail) { + let opt = item.documentation(); + let doc = opt.as_ref().map(|it| it.as_str()); + assert_eq!(doc, Some(expected.documentation)); + return; + } + } + panic!("completion detail not found: {}", expected.detail) + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, + ); + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, + ); + } +} diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d96cb559691..9636cd0d6af 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,8 @@ use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -10,12 +10,7 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, @@ -169,13 +164,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -183,7 +180,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -208,10 +206,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, src.value.short_label(), mod_path) } } @@ -951,4 +950,106 @@ fn func(foo: i32) { if true { <|>foo; }; } &["mod my"], ); } + + #[test] + fn test_hover_struct_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + #[doc = "bar docs"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr_multiple_and_mixed() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs 0 + #[doc = "bar docs 1"] + #[doc = "bar docs 2"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + ); + } } diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index bfc05e08bf2..a8f2454fd96 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs @@ -83,13 +83,22 @@ pub trait DocCommentsOwner: AstNode { CommentIter { iter: self.syntax().children_with_tokens() } } + fn doc_comment_text(&self) -> Option { + self.doc_comments().doc_comment_text() + } +} + +impl CommentIter { + pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter { + CommentIter { iter: syntax_node.children_with_tokens() } + } + /// Returns the textual content of a doc comment block as a single string. /// That is, strips leading `///` (+ optional 1 character of whitespace), /// trailing `*/`, trailing whitespace and then joins the lines. - fn doc_comment_text(&self) -> Option { + pub fn doc_comment_text(self) -> Option { let mut has_comments = false; let docs = self - .doc_comments() .filter(|comment| comment.kind().doc.is_some()) .map(|comment| { has_comments = true; diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc index 12812fa0be7..4b93b759fa4 100644 --- a/docs/user/generated_features.adoc +++ b/docs/user/generated_features.adoc @@ -76,7 +76,7 @@ Navigates to the type of an identifier. === Hover -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L63[hover.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L58[hover.rs] Shows additional information, like type of an expression or documentation for definition when "focusing" code. Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.