diff --git a/data/mrdocs/addons/generator/adoc/partials/doc/block/table.adoc.hbs b/data/mrdocs/addons/generator/adoc/partials/doc/block/table.adoc.hbs index 91b462b501e..c711a557e72 100644 --- a/data/mrdocs/addons/generator/adoc/partials/doc/block/table.adoc.hbs +++ b/data/mrdocs/addons/generator/adoc/partials/doc/block/table.adoc.hbs @@ -1,4 +1,4 @@ {{#if items.[0].is_header}}[%header] {{/if}}|=== -{{#each items}}{{#each cells}}| {{> doc/inline-container }} {{/each}} +{{#each items}}{{#each cells}}| {{#replace "|" "\|"}}{{> doc/inline-container }}{{/replace}} {{/each}} {{/each}}|=== diff --git a/include/mrdocs/Metadata/DocComment.hpp b/include/mrdocs/Metadata/DocComment.hpp index c768fce220e..80f2577dfde 100644 --- a/include/mrdocs/Metadata/DocComment.hpp +++ b/include/mrdocs/Metadata/DocComment.hpp @@ -244,6 +244,24 @@ traverseImpl(doc::ListItem& item, F&& func) } } +template +void +traverseImpl(doc::TableCell& cell, F&& func) +{ + if constexpr (!bottomUp && std::invocable) + { + func(cell); + } + for (Polymorphic& child : cell.children) + { + traverseImpl(child, func); + } + if constexpr (bottomUp && std::invocable) + { + func(cell); + } +} + // Traverse a derived node template requires(!std::same_as && !std::same_as) @@ -281,6 +299,17 @@ traverseImpl(NodeTy& N, F&& func) } } + if constexpr (std::same_as) + { + for (doc::TableRow& row: N.items) + { + for (doc::TableCell& cell: row.Cells) + { + traverseImpl(cell, func); + } + } + } + if constexpr (bottomUp && std::invocable) { func(N); diff --git a/src/mrdocs/AST/ExtractDocComment.cpp b/src/mrdocs/AST/ExtractDocComment.cpp index bb6c15609da..dc829a73e8b 100644 --- a/src/mrdocs/AST/ExtractDocComment.cpp +++ b/src/mrdocs/AST/ExtractDocComment.cpp @@ -303,12 +303,6 @@ class DocCommentVisitor // user's to fix, so their findings go to the debug log instead. bool warnings_{ true }; - // A `\f$...\f$` inline formula is a verbatim block command to Clang, so it - // splits the surrounding paragraph in two. When that happens we record the - // source line just past the formula; a paragraph that starts on the next - // line (no blank line between) is merged back so the formula stays inline. - unsigned mergeParagraphAfterLine_{ 0 }; - // --- inline assembly template InlineTy, class... Args> @@ -516,10 +510,8 @@ class DocCommentVisitor // --- Visiting using Cursor (no member iterators) void - visitChildrenWithCursor(clang::comments::Comment const* C) + visitCursor(Cursor cur) { - MRDOCS_COMMENT_TRACE(C, ctx_); - Cursor cur(C); while (!cur.done()) { auto* n = cur.cur(); @@ -529,6 +521,95 @@ class DocCommentVisitor } } + void + visitChildrenWithCursor(clang::comments::Comment const* C) + { + MRDOCS_COMMENT_TRACE(C, ctx_); + visitCursor(Cursor(C)); + } + + // --- Paragraphs split by inline formulas + + bool + isInlineFormula(clang::comments::Comment const* c) const + { + using namespace clang::comments; + return c->getCommentKind() == CommentKind::VerbatimBlockComment + && static_cast(c)->getCommandName( + ctx_.getCommentCommandTraits()) == "f$"; + } + + unsigned + lineOf(clang::SourceLocation loc) const + { + return sm_.getPresumedLoc(loc).getLine(); + } + + // Whether `next`, the sibling after `prev`, continues the paragraph + // `prev` belongs to: it is a formula, or the text after one, and no blank + // line separates the two. + bool + continuesParagraph( + clang::comments::Comment const* prev, + clang::comments::Comment const* next) const + { + using namespace clang::comments; + bool const joinable + = next != nullptr + && (isInlineFormula(next) + || (isInlineFormula(prev) + && next->getCommentKind() + == CommentKind::ParagraphComment)); + return joinable + && lineOf(next->getBeginLoc()) <= lineOf(prev->getEndLoc()) + 1; + } + + static void + appendToParagraph(Cursor& paragraph, clang::comments::Comment const* c) + { + if (c->getCommentKind() + == clang::comments::CommentKind::ParagraphComment) + { + paragraph.children.append(c->child_begin(), c->child_end()); + } + else + { + paragraph.children.push_back(c); + } + } + + // Clang ends a paragraph at a `\f$...\f$` inline formula, which it models + // as a verbatim block, and puts the text after the formula in a new + // paragraph. Collect the nodes of the paragraph (or formula) at `cur.i` + // and of the siblings that continue it, leaving `cur.i` at the last of + // them, so that they are visited as one paragraph: the formulas stay + // inline, and an HTML table around them finds its end tag. + Cursor + joinFormulaSplitParagraph(Cursor& cur) const + { + Cursor paragraph; + appendToParagraph(paragraph, cur.cur()); + while (continuesParagraph(cur.cur(), cur.peek())) + { + cur.advance(); + appendToParagraph(paragraph, cur.cur()); + } + return paragraph; + } + + void + visitTopLevelParagraph(Cursor& cur) + { + Cursor joined = joinFormulaSplitParagraph(cur); + doc::ParagraphBlock paragraph; + BlockScope scope = enterScope(paragraph); + visitCursor(std::move(joined)); + if (!paragraph.empty()) + { + jd_.Document.emplace_back(std::move(paragraph)); + } + } + // What sits between an inline HTML start tag and its matching end tag. // When every node in between is text, `text` holds it all and the span // becomes one inline node. Otherwise (inline commands such as `@c`, @@ -1087,7 +1168,7 @@ class DocCommentVisitor return; case CommentKind::ParagraphComment: - visitParagraph(static_cast(C)); + visitParagraph(static_cast(C), cur); return; case CommentKind::BlockCommandComment: @@ -1103,7 +1184,14 @@ class DocCommentVisitor return; case CommentKind::VerbatimBlockComment: - visitVerbatimBlock(static_cast(C)); + if (!curInlines_ && isInlineFormula(C)) + { + visitTopLevelParagraph(cur); + } + else + { + visitVerbatimBlock(static_cast(C)); + } return; case CommentKind::VerbatimBlockLineComment: @@ -1571,34 +1659,16 @@ class DocCommentVisitor } void - visitParagraph(clang::comments::ParagraphComment const* C) + visitParagraph(clang::comments::ParagraphComment const* C, Cursor& cur) { MRDOCS_COMMENT_TRACE(C, ctx_); if (curInlines_) { visitChildrenWithCursor(C); - return; - } - // Merge back a paragraph split off by a preceding `\f$...\f$` inline - // formula, but only when it is contiguous (no blank line between). - unsigned const mergeLine = std::exchange(mergeParagraphAfterLine_, 0); - if (mergeLine != 0 - && !jd_.Document.empty() - && jd_.Document.back()->Kind == doc::BlockKind::Paragraph - && sm_.getPresumedLoc(C->getBeginLoc()).getLine() <= mergeLine + 1) - { - auto& inlines = static_cast( - jd_.Document.back()->asParagraph()); - auto scope = enterScope(inlines); - visitChildrenWithCursor(C); - return; } - doc::ParagraphBlock paragraph; - auto scope = enterScope(paragraph); - visitChildrenWithCursor(C); - if (!paragraph.empty()) + else { - jd_.Document.emplace_back(std::move(paragraph)); + visitTopLevelParagraph(cur); } } @@ -1931,31 +2001,8 @@ class DocCommentVisitor if (name == "f$") { // Inline formula. Clang positions the verbatim block outside the - // surrounding paragraph, so emit into the current inline flow when - // one is open, append to the paragraph that was just closed, or - // otherwise wrap it in its own paragraph. - if (curInlines_) - { - emplaceInline(true, std::move(payload)); - return; - } - doc::InlineContainer* inlines = nullptr; - if (!jd_.Document.empty() - && jd_.Document.back()->Kind == doc::BlockKind::Paragraph) - { - inlines = &static_cast( - jd_.Document.back()->asParagraph()); - } - else - { - jd_.Document.emplace_back(doc::ParagraphBlock{}); - inlines = &static_cast( - jd_.Document.back()->asParagraph()); - } - inlines->children.emplace_back( - std::in_place_type, std::move(payload)); - mergeParagraphAfterLine_ - = sm_.getPresumedLoc(C->getEndLoc()).getLine(); + // surrounding paragraph; visitTopLevelParagraph puts it back. + emplaceInline(true, std::move(payload)); return; } if (name == "f[") diff --git a/tests/golden/fixtures/javadoc/html-table/html-table-math.adoc b/tests/golden/fixtures/javadoc/html-table/html-table-math.adoc new file mode 100644 index 00000000000..a9053048d39 --- /dev/null +++ b/tests/golden/fixtures/javadoc/html-table/html-table-math.adoc @@ -0,0 +1,50 @@ += Reference +:mrdocs: + +[#index] +== Global index + +=== Functions + +[cols="1,4"] +|=== +| Name| Description +| link:#norm[`norm`] +| Compute the norm of a vector. +|=== + + +[#norm] +== norm + +Compute the norm of a vector. + +=== Synopsis + +Declared in `<html‐table‐math.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +double +norm( + double x, + double y, + int p); +---- + +=== Description + +[%header] +|=== +| Norm | Formula +| Euclidean | stem:[\sqrt{x^2 + y^2}] +| Maximum | stem:[\max(\|x\|, \|y\|)] +|=== +stem:[p] selects the norm. + +The result is stem:[\|v\|_p]. + +stem:[p = 2] is the default. + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/tests/golden/fixtures/javadoc/html-table/html-table-math.cpp b/tests/golden/fixtures/javadoc/html-table/html-table-math.cpp new file mode 100644 index 00000000000..f2fee409c95 --- /dev/null +++ b/tests/golden/fixtures/javadoc/html-table/html-table-math.cpp @@ -0,0 +1,15 @@ +/** Compute the norm of a vector. + + + + + +
NormFormula
Euclidean@f$ \sqrt{x^2 + y^2} @f$
Maximum$\max(|x|, |y|)$
+ + \f$p\f$ selects the norm. + + The result is \f$\|v\|_p\f$. + + \f$p = 2\f$ is the default. + */ +double norm(double x, double y, int p); diff --git a/tests/golden/fixtures/javadoc/html-table/html-table-math.xml b/tests/golden/fixtures/javadoc/html-table/html-table-math.xml new file mode 100644 index 00000000000..18301193ff8 --- /dev/null +++ b/tests/golden/fixtures/javadoc/html-table/html-table-math.xml @@ -0,0 +1,213 @@ + + + + index + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + FXSuomuVJ2mFDTFHnMYFugnvejt + + + + norm + norm + + + + html-table-math.cpp + html-table-math.cpp + 15 + 1 + + + + + function + FXSuomuVJ2mFDTFHnMYFugnvejt + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + + table + + + + + + + + text + Norm + + + + + + + text + Formula + + + + + + + + + + + text + Euclidean + + + + + + + math + \sqrt{x^2 + y^2} + + + + + + + + + + + text + Maximum + + + + + + + math + \max(|x|, |y|) + + + + + + +
+ + paragraph + + + math + p + + + text + selects the norm. + + + + + paragraph + + + text + The result is + + + math + \|v\|_p + + + text + . + + + + + paragraph + + + math + p = 2 + + + text + is the default. + + + +
+ + brief + + + text + Compute the norm of a vector. + + + +
+ + + named + + + identifier + double + + + double + + + + + + + named + + + identifier + double + + + double + + + x + + + + + named + + + identifier + double + + + double + + + y + + + + + named + + + identifier + int + + + int + + + p + + + normal +
+
diff --git a/tests/golden/fixtures/javadoc/html-table/html-table-math.yml b/tests/golden/fixtures/javadoc/html-table/html-table-math.yml new file mode 100644 index 00000000000..d5fd5e08fab --- /dev/null +++ b/tests/golden/fixtures/javadoc/html-table/html-table-math.yml @@ -0,0 +1,2 @@ +generator: [adoc, xml] +multipage: false