use super::*;
pub(super) fn section_body_parts(section: &RoxygenSection) -> Vec<Vec<Inline>> {
let mut groups: Vec<Vec<Inline>> = Vec::new();
let mut cur: Vec<Inline> = Vec::new();
for el in section.syntax().children_with_tokens() {
match el.kind() {
SyntaxKind::ROXYGEN_MD_HEADING => {
let Some(node) = el.into_node() else { continue };
if !cur.is_empty() {
groups.push(std::mem::take(&mut cur));
}
groups.push(vec![Inline::MdHeading(node)]);
}
SyntaxKind::ROXYGEN_PARAGRAPH
| SyntaxKind::ROXYGEN_RD_MACRO
| SyntaxKind::ROXYGEN_MD_LIST
| SyntaxKind::ROXYGEN_MD_CODE_BLOCK
| SyntaxKind::ROXYGEN_MD_INDENTED_CODE
| SyntaxKind::ROXYGEN_MD_HTML_BLOCK
| SyntaxKind::ROXYGEN_MD_BLOCK_QUOTE
| SyntaxKind::ROXYGEN_MD_TABLE => {
let Some(node) = el.into_node() else { continue };
let kind = node.kind();
let inlines = match kind {
SyntaxKind::ROXYGEN_PARAGRAPH => RoxygenParagraph::cast(node)
.map(|p| paragraph_inlines(&p))
.unwrap_or_default(),
SyntaxKind::ROXYGEN_MD_LIST => vec![Inline::MdList(node)],
SyntaxKind::ROXYGEN_MD_CODE_BLOCK => vec![Inline::MdCodeBlock(node)],
SyntaxKind::ROXYGEN_MD_INDENTED_CODE => vec![Inline::MdIndentedCode(node)],
SyntaxKind::ROXYGEN_MD_HTML_BLOCK => vec![Inline::MdHtmlBlock(node)],
SyntaxKind::ROXYGEN_MD_BLOCK_QUOTE => vec![Inline::MdBlockQuote(node)],
SyntaxKind::ROXYGEN_MD_TABLE => vec![Inline::MdTable(node)],
_ => vec![Inline::Macro(node)],
};
if !cur.is_empty() && kind != SyntaxKind::ROXYGEN_MD_BLOCK_QUOTE {
cur.push(Inline::Text(" ".to_string()));
}
cur.extend(inlines);
}
SyntaxKind::ROXYGEN_MD_THEMATIC_BREAK if !cur.is_empty() => {
groups.push(std::mem::take(&mut cur));
}
SyntaxKind::ROXYGEN_MARKER if !cur.is_empty() => {
groups.push(std::mem::take(&mut cur));
}
_ => {}
}
}
if !cur.is_empty() {
groups.push(cur);
}
groups
}
pub(super) fn paragraph_inlines(para: &RoxygenParagraph) -> Vec<Inline> {
let mut out = Vec::new();
for el in para.syntax().children_with_tokens() {
match el.kind() {
SyntaxKind::ROXYGEN_MARKER => {} SyntaxKind::NEWLINE => out.push(Inline::Text(SOFT_BREAK.to_string())), _ => push_inline(&mut out, el),
}
}
out
}
pub(super) fn tag_inlines(tag: &RoxygenTag) -> Vec<Inline> {
let mut out = Vec::new();
let mut seen_prose = false;
for el in tag.syntax().children_with_tokens() {
match el.kind() {
SyntaxKind::ROXYGEN_AT | SyntaxKind::ROXYGEN_TAG_NAME | SyntaxKind::ROXYGEN_TAG_ARG => {
continue;
}
SyntaxKind::ROXYGEN_MARKER => {}
SyntaxKind::NEWLINE => {
if seen_prose {
out.push(Inline::Text(SOFT_BREAK.to_string()));
}
}
SyntaxKind::WHITESPACE => {
if seen_prose {
push_inline(&mut out, el);
}
}
_ => {
seen_prose = true;
push_inline(&mut out, el);
}
}
}
out
}
pub(super) fn push_inline(
out: &mut Vec<Inline>,
el: NodeOrToken<SyntaxNode, crate::syntax::SyntaxToken>,
) {
match el {
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_RD_MACRO => {
out.push(Inline::Macro(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_LIST => {
out.push(Inline::MdList(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_CODE_BLOCK => {
out.push(Inline::MdCodeBlock(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_INDENTED_CODE => {
out.push(Inline::MdIndentedCode(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_BLOCK_QUOTE => {
out.push(Inline::MdBlockQuote(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_TABLE => {
out.push(Inline::MdTable(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_HTML_BLOCK => {
out.push(Inline::MdHtmlBlock(n));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_THEMATIC_BREAK => {}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_HEADING => {
out.push(Inline::MdHeading(n));
}
NodeOrToken::Node(n)
if matches!(
n.kind(),
SyntaxKind::ROXYGEN_MD_EMPH | SyntaxKind::ROXYGEN_MD_STRONG
) =>
{
let strong = n.kind() == SyntaxKind::ROXYGEN_MD_STRONG;
let kids: Vec<_> = n.children_with_tokens().collect();
let interior = kids.len().saturating_sub(1);
let mut children = Vec::new();
for child in kids.into_iter().take(interior).skip(1) {
match child.kind() {
SyntaxKind::ROXYGEN_MARKER => {} SyntaxKind::NEWLINE => children.push(Inline::Text(SOFT_BREAK.to_string())),
_ => push_inline(&mut children, child),
}
}
out.push(Inline::MdEmphasis { strong, children });
}
NodeOrToken::Node(n)
if n.kind() == SyntaxKind::ROXYGEN_MD_LINK
&& n.text().char_at(0.into()) == Some('<') =>
{
out.push(Inline::MdLink(n.text().to_string()));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_LINK => {
let kids: Vec<_> = n.children_with_tokens().collect();
let cross_dest = kids
.iter()
.position(|c| c.kind() == SyntaxKind::ROXYGEN_MD_DELIM && c.to_string() == "](");
let (interior, closer) = match cross_dest {
Some(idx) => {
let mut close = String::from("](");
for c in &kids[idx + 1..] {
match c.kind() {
SyntaxKind::ROXYGEN_MARKER | SyntaxKind::WHITESPACE => {}
SyntaxKind::NEWLINE => close.push('\n'),
_ => close.push_str(&c.to_string()),
}
}
(idx, close)
}
None => (
kids.len().saturating_sub(1),
kids.last().map(|c| c.to_string()).unwrap_or_default(),
),
};
let mut display = Vec::new();
let mut after_marker = false;
for child in kids.into_iter().take(interior).skip(1) {
match child.kind() {
SyntaxKind::ROXYGEN_MARKER => after_marker = true, SyntaxKind::NEWLINE => {
after_marker = false;
display.push(Inline::Text(SOFT_BREAK.to_string()));
}
SyntaxKind::WHITESPACE if after_marker => {
after_marker = false;
let text = child.to_string();
let content = text.strip_prefix(' ').unwrap_or(&text);
if !content.is_empty() {
display.push(Inline::Text(content.to_string()));
}
}
_ => {
after_marker = false;
push_inline(&mut display, child);
}
}
}
if closer == "]" {
out.push(Inline::MdShortcutLink { display });
} else if let Some(dest) = closer.strip_prefix("][").and_then(|s| s.strip_suffix(']')) {
out.push(Inline::MdRefLink {
dest: dest.to_string(),
display,
});
} else {
out.push(Inline::MdInlineLink {
url: inline_link_dest(&closer),
display,
});
}
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_HTML => {
out.push(Inline::MdHtml(md_html_node_text(&n)));
}
NodeOrToken::Node(n) if n.kind() == SyntaxKind::ROXYGEN_MD_CODE => {
out.push(Inline::MdCode(strip_code_span(&md_html_node_text(&n))));
}
NodeOrToken::Node(n) => out.push(Inline::Text(n.text().to_string())),
NodeOrToken::Token(t) if t.kind() == SyntaxKind::ROXYGEN_MD_CODE => {
out.push(Inline::MdCode(strip_code_span(t.text())));
}
NodeOrToken::Token(t)
if t.kind() == SyntaxKind::ROXYGEN_MD_LINK && resolve_md_link(t.text()).is_some() =>
{
out.push(Inline::MdLink(t.text().to_string()));
}
NodeOrToken::Token(t)
if t.kind() == SyntaxKind::ROXYGEN_MD_IMAGE
&& (resolve_md_image(t.text()).is_some() || image_is_collapsed(t.text())) =>
{
out.push(Inline::MdImage(t.text().to_string()));
}
NodeOrToken::Token(t) if t.kind() == SyntaxKind::ROXYGEN_MD_HTML => {
out.push(Inline::MdHtml(t.text().to_string()));
}
NodeOrToken::Token(t) => out.push(Inline::Text(t.text().to_string())),
}
}
fn md_html_node_text(node: &SyntaxNode) -> String {
let mut s = String::new();
for el in node.children_with_tokens() {
match el.kind() {
SyntaxKind::ROXYGEN_MARKER | SyntaxKind::WHITESPACE => {}
SyntaxKind::NEWLINE => s.push('\n'),
_ => match el {
NodeOrToken::Token(t) => s.push_str(t.text()),
NodeOrToken::Node(n) => s.push_str(&n.text().to_string()),
},
}
}
s
}