use super::build::{
block_macro_opener_closes, emit_block_macro, emit_block_macro_inline, emit_md_block_quote,
emit_md_code_block, emit_md_heading, emit_md_html_block, emit_md_indented_code, emit_md_list,
emit_md_setext_heading, emit_md_table, emit_md_thematic_break, is_block_macro_line,
is_block_macro_opener, is_md_block_quote_start, is_md_code_block_start, is_md_heading_start,
is_md_html_block_start, is_md_indented_code_start, is_md_list_start,
is_md_setext_heading_start, is_md_setext_underline_line, is_md_setext_underline_or_dash,
is_md_table_start, is_md_thematic_break_line,
};
use crate::parser::events::Event;
use crate::parser::lexer::{RoxygenRole, TokKind, Token};
use crate::syntax::SyntaxKind;
pub(crate) fn emit_roxygen_block(tokens: &[Token], start: usize, events: &mut Vec<Event>) -> usize {
let mut block = Vec::new();
let end = emit_roxygen_block_events(tokens, start, &mut block);
super::inline::resolve_emphasis(tokens, &mut block);
events.append(&mut block);
end
}
fn block_md(tokens: &[Token], start: usize) -> bool {
let mut md = false;
let mut i = start;
loop {
let mut line = String::from(&*tokens[i].text);
let mut j = i + 1;
while tokens.get(j).is_some_and(|t| is_line_body_kind(&t.kind)) {
line.push_str(&tokens[j].text);
j += 1;
}
if let Some(on) = super::lex::roxygen_md_directive(&line) {
md = on;
}
if tokens.get(j).map(|t| &t.kind) != Some(&TokKind::Newline) {
break;
}
let mut m = j + 1;
while tokens.get(m).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
m += 1;
}
if tokens.get(m).map(|t| &t.kind) == Some(&TokKind::RoxygenMarker) {
i = m;
} else {
break;
}
}
md
}
fn emit_roxygen_block_events(tokens: &[Token], start: usize, events: &mut Vec<Event>) -> usize {
let md = block_md(tokens, start);
debug_assert_eq!(tokens[start].kind, TokKind::RoxygenMarker);
events.push(Event::Start(SyntaxKind::ROXYGEN_BLOCK));
let mut i = start;
let mut section_open = false;
let mut para_open = false;
loop {
if is_md_indented_code_start(tokens, i, para_open, md) {
if !section_open {
events.push(Event::Start(SyntaxKind::ROXYGEN_SECTION));
section_open = true;
}
i = emit_md_indented_code(tokens, i, events);
} else {
match classify_line(tokens, i) {
LineKind::Tag => {
if para_open {
events.push(Event::Finish); para_open = false;
}
if section_open {
events.push(Event::Finish); }
events.push(Event::Start(SyntaxKind::ROXYGEN_SECTION));
section_open = true;
i = emit_tag_line(tokens, i, events);
}
LineKind::Blank => {
if para_open {
events.push(Event::Finish); para_open = false;
}
if !section_open {
events.push(Event::Start(SyntaxKind::ROXYGEN_SECTION));
section_open = true;
}
i = emit_line_tokens(tokens, i, events); }
LineKind::Prose => {
if !section_open {
events.push(Event::Start(SyntaxKind::ROXYGEN_SECTION));
section_open = true;
}
if is_md_html_block_start(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_html_block(tokens, i, events);
} else if is_md_block_quote_start(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_block_quote(tokens, i, events);
} else if is_md_code_block_start(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_code_block(tokens, i, events);
} else if is_md_list_start(tokens, i, para_open) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_list(tokens, i, events);
} else if is_block_macro_line(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_block_macro(tokens, i, events);
} else if is_md_table_start(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_table(tokens, i, events);
} else if is_md_heading_start(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_heading(tokens, i, events);
} else if !para_open && is_md_setext_heading_start(tokens, i) {
i = emit_md_setext_heading(tokens, i, events);
} else if is_md_thematic_break_line(tokens, i) {
if para_open {
events.push(Event::Finish); para_open = false;
}
i = emit_md_thematic_break(tokens, i, events);
} else {
if !para_open {
events.push(Event::Start(SyntaxKind::ROXYGEN_PARAGRAPH));
para_open = true;
}
i = emit_prose_line(tokens, i, events);
}
}
}
}
if tokens.get(i).map(|t| &t.kind) == Some(&TokKind::Newline) {
let mut m = i + 1;
while tokens.get(m).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
m += 1;
}
if tokens.get(m).map(|t| &t.kind) == Some(&TokKind::RoxygenMarker) {
for idx in i..m {
events.push(Event::Tok(idx));
}
i = m;
continue;
}
}
break;
}
if para_open {
events.push(Event::Finish); }
if section_open {
events.push(Event::Finish); }
events.push(Event::Finish); i
}
pub(super) enum LineKind {
Tag,
Blank,
Prose,
}
pub(super) fn classify_line(tokens: &[Token], start: usize) -> LineKind {
let content = line_content_start(tokens, start);
let mut i = content;
while let Some(tok) = tokens.get(i) {
match tok.kind.roxygen_role() {
Some(RoxygenRole::At) => return LineKind::Tag,
Some(RoxygenRole::Content) => return LineKind::Prose,
_ if tok.kind == TokKind::Whitespace => i += 1,
_ => break,
}
}
LineKind::Blank
}
pub(super) fn line_content_start(tokens: &[Token], marker: usize) -> usize {
let mut i = marker + 1;
while tokens.get(i).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
i += 1;
}
i
}
pub(super) fn is_line_body_kind(kind: &TokKind) -> bool {
matches!(kind, TokKind::Whitespace)
|| matches!(kind.roxygen_role(), Some(role) if role != RoxygenRole::Marker)
}
fn emit_line_tokens(tokens: &[Token], start: usize, events: &mut Vec<Event>) -> usize {
events.push(Event::Tok(start)); let mut i = start + 1;
while tokens.get(i).is_some_and(|t| is_line_body_kind(&t.kind)) {
events.push(Event::Tok(i));
i += 1;
}
i
}
fn emit_prose_line(tokens: &[Token], start: usize, events: &mut Vec<Event>) -> usize {
events.push(Event::Tok(start)); let mut i = start + 1;
while let Some(tok) = tokens.get(i) {
if !is_line_body_kind(&tok.kind) {
break;
}
if tok.kind == TokKind::RoxygenText
&& is_block_macro_opener(&tok.text)
&& block_macro_opener_closes(tokens, i)
{
i = emit_block_macro_inline(tokens, i, events);
continue;
}
events.push(Event::Tok(i));
i += 1;
}
i
}
fn emit_tag_line(tokens: &[Token], start: usize, events: &mut Vec<Event>) -> usize {
events.push(Event::Tok(start)); let mut i = start + 1;
while tokens.get(i).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
events.push(Event::Tok(i)); i += 1;
}
events.push(Event::Start(SyntaxKind::ROXYGEN_TAG));
let mut tag_name: Option<&str> = None;
let mut value_start: Option<usize> = None;
let mut j = i;
while tokens.get(j).is_some_and(|t| is_line_body_kind(&t.kind)) {
let tok = &tokens[j];
if tok.kind == TokKind::RoxygenTagName {
tag_name = Some(&tok.text);
}
if tok.kind.roxygen_role() == Some(RoxygenRole::Content) {
value_start = Some(j);
break;
}
j += 1;
}
let has_value = value_start.is_some();
if has_value
&& tag_name.is_some_and(super::tag_folds_prose_continuation)
&& is_md_setext_heading_start(tokens, start)
{
let mut head_end = value_start.unwrap();
while head_end > i && tokens[head_end - 1].kind == TokKind::Whitespace {
head_end -= 1;
}
for idx in i..head_end {
events.push(Event::Tok(idx));
}
events.push(Event::Finish); return emit_md_setext_heading_from_value(tokens, head_end, events);
}
while tokens.get(i).is_some_and(|t| is_line_body_kind(&t.kind)) {
events.push(Event::Tok(i));
i += 1;
}
if has_value && tag_name.is_some_and(super::tag_folds_prose_continuation) {
while tokens.get(i).map(|t| &t.kind) == Some(&TokKind::Newline) {
let mut m = i + 1;
while tokens.get(m).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
m += 1;
}
if tokens.get(m).map(|t| &t.kind) != Some(&TokKind::RoxygenMarker)
|| !is_foldable_continuation(tokens, m)
{
break;
}
for idx in i..m {
events.push(Event::Tok(idx));
}
i = emit_prose_line(tokens, m, events);
}
}
events.push(Event::Finish); i
}
fn emit_md_setext_heading_from_value(
tokens: &[Token],
value_start: usize,
events: &mut Vec<Event>,
) -> usize {
events.push(Event::Start(SyntaxKind::ROXYGEN_MD_HEADING));
let mut i = value_start;
while tokens.get(i).is_some_and(|t| is_line_body_kind(&t.kind)) {
events.push(Event::Tok(i));
i += 1;
}
loop {
let mut m = i + 1;
while tokens.get(m).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
m += 1;
}
debug_assert_eq!(
tokens.get(m).map(|t| &t.kind),
Some(&TokKind::RoxygenMarker),
"setext heading run terminates in an underline"
);
for idx in i..=m {
events.push(Event::Tok(idx)); }
let mut k = m + 1;
while tokens.get(k).is_some_and(|t| is_line_body_kind(&t.kind)) {
events.push(Event::Tok(k));
k += 1;
}
if is_md_setext_underline_or_dash(tokens, m) {
events.push(Event::Finish); return k;
}
i = k;
}
}
pub(super) fn is_foldable_continuation(tokens: &[Token], marker: usize) -> bool {
matches!(classify_line(tokens, marker), LineKind::Prose)
&& !is_md_html_block_start(tokens, marker)
&& !is_md_code_block_start(tokens, marker)
&& !is_md_list_start(tokens, marker, true)
&& !is_block_macro_line(tokens, marker)
&& !is_md_table_start(tokens, marker)
&& !is_md_heading_start(tokens, marker)
&& !is_md_setext_underline_line(tokens, marker)
&& !is_md_block_quote_start(tokens, marker)
&& !is_md_thematic_break_line(tokens, marker)
}
pub(super) fn next_roxygen_line_marker(tokens: &[Token], marker: usize) -> Option<usize> {
let mut i = marker + 1;
while tokens.get(i).is_some_and(|t| is_line_body_kind(&t.kind)) {
i += 1;
}
if tokens.get(i).map(|t| &t.kind) != Some(&TokKind::Newline) {
return None;
}
let mut m = i + 1;
while tokens.get(m).map(|t| &t.kind) == Some(&TokKind::Whitespace) {
m += 1;
}
(tokens.get(m).map(|t| &t.kind) == Some(&TokKind::RoxygenMarker)).then_some(m)
}