diff --git a/crates/ra_hir/src/docs.rs b/crates/ra_hir/src/docs.rs index b1b47af9e6..5db72c08a3 100644 --- a/crates/ra_hir/src/docs.rs +++ b/crates/ra_hir/src/docs.rs @@ -27,10 +27,5 @@ pub trait Docs { } pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - let comments = node.doc_comment_text(); - if comments.is_empty() { - None - } else { - Some(Documentation::new(&comments)) - } + node.doc_comment_text().map(|it| Documentation::new(&it)) } diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index f203a6bf11..ee1e137993 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -126,8 +126,7 @@ impl CallInfo { }; let mut doc = None; - let docs = node.doc_comment_text(); - if !docs.is_empty() { + if let Some(docs) = node.doc_comment_text() { // Massage markdown let mut processed_lines = Vec::new(); let mut in_code_block = false; diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index ff9ae2d9c8..f993a461c3 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -100,12 +100,7 @@ impl NavigationTarget { fn docs(&self, db: &RootDatabase) -> Option { let node = self.node(db)?; fn doc_comments(node: &N) -> Option { - let comments = node.doc_comment_text(); - if comments.is_empty() { - None - } else { - Some(comments) - } + node.doc_comment_text() } visitor() diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index ab3dd1b84d..3d22a88f35 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -115,21 +115,38 @@ pub trait DocCommentsOwner: AstNode { } /// Returns the textual content of a doc comment block as a single string. - /// That is, strips leading `///` and joins lines - fn doc_comment_text(&self) -> std::string::String { - self.doc_comments() + /// That is, strips leading `///` (+ optional 1 character of whitespace) + /// and joins lines. + fn doc_comment_text(&self) -> Option { + let docs = self + .doc_comments() .filter(|comment| comment.is_doc_comment()) .map(|comment| { - let prefix = comment.prefix(); - let trimmed = comment - .text() - .as_str() - .trim() - .trim_start_matches(prefix) - .trim_start(); - trimmed.to_owned() + let prefix_len = comment.prefix().len(); + + let line = comment.text().as_str(); + + // Determine if the prefix or prefix + 1 char is stripped + let pos = if line + .chars() + .nth(prefix_len) + .map(|c| c.is_whitespace()) + .unwrap_or(false) + { + prefix_len + 1 + } else { + prefix_len + }; + + line[pos..].to_owned() }) - .join("\n") + .join("\n"); + + if docs.is_empty() { + None + } else { + Some(docs) + } } } @@ -703,6 +720,18 @@ impl BindPat { } } +#[test] +fn test_doc_comment_none() { + let file = SourceFile::parse( + r#" + // non-doc + mod foo {} + "#, + ); + let module = file.syntax().descendants().find_map(Module::cast).unwrap(); + assert!(module.doc_comment_text().is_none()); +} + #[test] fn test_doc_comment_of_items() { let file = SourceFile::parse( @@ -713,5 +742,25 @@ fn test_doc_comment_of_items() { "#, ); let module = file.syntax().descendants().find_map(Module::cast).unwrap(); - assert_eq!("doc", module.doc_comment_text()); + assert_eq!("doc", module.doc_comment_text().unwrap()); +} + +#[test] +fn test_doc_comment_preserves_indents() { + let file = SourceFile::parse( + r#" + /// doc1 + /// ``` + /// fn foo() { + /// // ... + /// } + /// ``` + mod foo {} + "#, + ); + let module = file.syntax().descendants().find_map(Module::cast).unwrap(); + assert_eq!( + "doc1\n```\nfn foo() {\n // ...\n}\n```", + module.doc_comment_text().unwrap() + ); }