use crate::options::ParserOptions;
use rowan::GreenNodeBuilder;
use std::any::Any;
use super::blocks::blockquotes::{
can_start_blockquote, count_blockquote_markers, emit_one_blockquote_marker,
strip_n_blockquote_markers,
};
use super::blocks::code_blocks::{
CodeBlockType, FenceInfo, InfoString, is_closing_fence, is_gfm_math_fence,
parse_fenced_code_block, parse_fenced_math_block, try_parse_fence_open,
};
use super::blocks::definition_lists::{
next_line_is_definition_marker, try_parse_definition_marker,
};
use super::blocks::fenced_divs::{DivFenceInfo, is_div_closing_fence, try_parse_div_fence_open};
use super::blocks::figures::parse_figure;
use super::blocks::headings::{
emit_atx_heading, emit_setext_heading, try_parse_atx_heading, try_parse_setext_heading,
};
use super::blocks::horizontal_rules::{emit_horizontal_rule, try_parse_horizontal_rule};
use super::blocks::html_blocks::{HtmlBlockType, parse_html_block, try_parse_html_block_start};
use super::blocks::indented_code::{is_indented_code_line, parse_indented_code_block};
use super::blocks::latex_envs::LatexEnvInfo;
use super::blocks::line_blocks::{parse_line_block, try_parse_line_block_start};
use super::blocks::lists::{
ListDelimiter, ListMarker, OrderedMarker, is_content_nested_bullet_marker,
try_parse_list_marker,
};
use super::blocks::metadata::{
emit_yaml_block, find_yaml_block_closing_pos, try_parse_mmd_title_block,
try_parse_pandoc_title_block, try_parse_yaml_block,
};
use super::blocks::raw_blocks;
use super::blocks::raw_blocks::extract_environment_name;
use super::blocks::reference_links::{
line_is_mmd_link_attribute_continuation, try_parse_footnote_marker,
try_parse_reference_definition,
};
use super::blocks::tables::{
is_caption_followed_by_table, try_parse_grid_table, try_parse_multiline_table,
try_parse_pipe_table, try_parse_simple_table,
};
use super::inlines::links::try_parse_inline_image;
use super::utils::container_stack::{byte_index_at_column, leading_indent};
use super::utils::helpers::strip_newline;
use super::utils::marker_utils::parse_blockquote_marker_info;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ListIndentInfo {
pub content_col: usize,
}
pub(crate) struct BlockContext<'a> {
pub content: &'a str,
pub has_blank_before: bool,
pub has_blank_before_strict: bool,
pub in_fenced_div: bool,
pub at_document_start: bool,
pub blockquote_depth: usize,
pub config: &'a ParserOptions,
pub content_indent: usize,
pub indent_to_emit: Option<&'a str>,
pub list_indent_info: Option<ListIndentInfo>,
pub in_list: bool,
pub next_line: Option<&'a str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BlockDetectionResult {
Yes,
YesCanInterrupt,
No,
}
pub(crate) struct PreparedBlockMatch {
pub parser_index: usize,
pub detection: BlockDetectionResult,
pub effect: BlockEffect,
pub payload: Option<Box<dyn Any>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BlockEffect {
None,
OpenFencedDiv,
CloseFencedDiv,
OpenFootnoteDefinition,
OpenList,
OpenDefinitionList,
OpenBlockQuote,
}
pub(crate) trait BlockParser {
fn effect(&self) -> BlockEffect {
BlockEffect::None
}
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)>;
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize;
fn name(&self) -> &'static str;
}
pub(crate) struct HorizontalRuleParser;
impl BlockParser for HorizontalRuleParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.has_blank_before {
return None;
}
if try_parse_horizontal_rule(ctx.content).is_some() {
Some((BlockDetectionResult::Yes, None))
} else {
None
}
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
emit_horizontal_rule(builder, ctx.content);
1 }
fn name(&self) -> &'static str {
"horizontal_rule"
}
}
pub(crate) struct AtxHeadingParser;
impl BlockParser for AtxHeadingParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if ctx.config.extensions.blank_before_header && !ctx.has_blank_before {
return None;
}
let level = try_parse_atx_heading(ctx.content)?;
Some((BlockDetectionResult::Yes, Some(Box::new(level))))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let heading_level = payload
.and_then(|p| p.downcast_ref::<usize>().copied())
.or_else(|| try_parse_atx_heading(ctx.content))
.unwrap_or(1);
emit_atx_heading(builder, ctx.content, heading_level, ctx.config);
1
}
fn name(&self) -> &'static str {
"atx_heading"
}
}
pub(crate) struct PandocTitleBlockParser;
impl BlockParser for PandocTitleBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.pandoc_title_block {
return None;
}
if !ctx.at_document_start || line_pos != 0 {
return None;
}
if !ctx.content.trim_start().starts_with('%') {
return None;
}
Some((BlockDetectionResult::Yes, None))
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
let new_pos =
try_parse_pandoc_title_block(lines, line_pos, builder).unwrap_or(line_pos + 1);
new_pos - line_pos
}
fn name(&self) -> &'static str {
"pandoc_title_block"
}
}
pub(crate) struct MmdTitleBlockParser;
impl BlockParser for MmdTitleBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.mmd_title_block {
return None;
}
if !ctx.at_document_start || line_pos != 0 || ctx.blockquote_depth > 0 {
return None;
}
if ctx.content.trim().is_empty() || !ctx.content.contains(':') {
return None;
}
Some((BlockDetectionResult::Yes, None))
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
let new_pos = try_parse_mmd_title_block(lines, line_pos, builder).unwrap_or(line_pos + 1);
new_pos - line_pos
}
fn name(&self) -> &'static str {
"mmd_title_block"
}
}
pub(crate) struct YamlMetadataParser;
#[derive(Debug, Clone)]
pub(crate) struct YamlMetadataPrepared {
pub at_document_start: bool,
pub closing_pos: usize,
}
impl BlockParser for YamlMetadataParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.yaml_metadata_block {
return None;
}
if ctx.blockquote_depth > 0 {
return None;
}
if ctx.content.trim() != "---" {
return None;
}
if !ctx.has_blank_before && !ctx.at_document_start {
return None;
}
let next_line = lines.get(line_pos + 1)?;
if next_line.trim().is_empty() {
return None;
}
let closing_pos = find_yaml_block_closing_pos(lines, line_pos, ctx.at_document_start)?;
Some((
BlockDetectionResult::Yes,
Some(Box::new(YamlMetadataPrepared {
at_document_start: ctx.at_document_start,
closing_pos,
})),
))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
if let Some(prepared) = payload.and_then(|p| p.downcast_ref::<YamlMetadataPrepared>())
&& let Some(new_pos) = emit_yaml_block(lines, line_pos, prepared.closing_pos, builder)
{
return new_pos - line_pos;
}
let at_document_start = payload
.and_then(|p| p.downcast_ref::<YamlMetadataPrepared>())
.map(|p| p.at_document_start)
.unwrap_or(ctx.at_document_start);
try_parse_yaml_block(lines, line_pos, builder, at_document_start)
.map(|new_pos| new_pos - line_pos)
.unwrap_or(1)
}
fn name(&self) -> &'static str {
"yaml_metadata"
}
}
pub(crate) struct FigureParser;
impl BlockParser for FigureParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.has_blank_before {
return None;
}
let trimmed = ctx.content.trim();
if !trimmed.starts_with("![") {
return None;
}
let (len, _alt, _dest, _attrs) = try_parse_inline_image(trimmed)?;
let after_image = &trimmed[len..];
if !after_image.trim().is_empty() {
return None;
}
Some((BlockDetectionResult::Yes, Some(Box::new(len))))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let _len = payload.and_then(|p| p.downcast_ref::<usize>().copied());
let line = lines[line_pos];
parse_figure(builder, line, ctx.config);
1
}
fn name(&self) -> &'static str {
"figure"
}
}
pub(crate) struct ReferenceDefinitionParser;
#[derive(Debug, Clone, Copy)]
struct ReferenceDefinitionPrepared {
consumed_lines: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct FootnoteDefinitionPrepared {
pub content_start: usize,
}
#[derive(Debug, Clone)]
pub(crate) struct BlockQuotePrepared {
pub depth: usize,
pub marker_info: Vec<crate::parser::utils::marker_utils::BlockQuoteMarkerInfo>,
#[allow(dead_code)]
pub inner_content: String,
pub can_start: bool,
pub can_nest: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct ListPrepared {
pub marker: ListMarker,
pub marker_len: usize,
pub spaces_after: usize,
pub spaces_after_cols: usize,
pub indent_cols: usize,
pub indent_bytes: usize,
pub nested_marker: Option<char>,
}
#[derive(Debug, Clone)]
pub(crate) enum DefinitionPrepared {
Term {
blank_count: usize,
},
Definition {
marker_char: char,
indent: usize,
spaces_after: usize,
spaces_after_cols: usize,
has_content: bool,
},
}
pub(crate) struct ListParser;
pub(crate) struct DefinitionListParser;
pub(crate) struct BlockQuoteParser;
impl BlockParser for ListParser {
fn effect(&self) -> BlockEffect {
BlockEffect::OpenList
}
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
let marker_match = try_parse_list_marker(ctx.content, ctx.config)?;
let after_marker_text = {
let (_, indent_bytes) = super::utils::container_stack::leading_indent(ctx.content);
let marker_end = indent_bytes + marker_match.marker_len;
if marker_end <= ctx.content.len() {
&ctx.content[marker_end..]
} else {
""
}
};
if marker_match.spaces_after_cols == 0 {
return None;
}
if !ctx.has_blank_before
&& ctx.in_list
&& matches!(
marker_match.marker,
ListMarker::Ordered(OrderedMarker::Decimal {
style: ListDelimiter::RightParen,
..
})
)
&& after_marker_text.trim() == ")"
{
return None;
}
if (ctx.has_blank_before || ctx.at_document_start)
&& try_parse_horizontal_rule(ctx.content).is_some()
{
return None;
}
let (indent_cols, indent_bytes) =
super::utils::container_stack::leading_indent(ctx.content);
if !ctx.has_blank_before
&& ctx.in_list
&& let Some(list_indent) = ctx.list_indent_info
&& list_indent.content_col >= 4
&& indent_cols == list_indent.content_col
&& indent_cols <= 4
&& matches!(
marker_match.marker,
ListMarker::Ordered(OrderedMarker::Decimal {
style: ListDelimiter::Parens,
..
}) | ListMarker::Ordered(OrderedMarker::Decimal {
style: ListDelimiter::Period,
..
}) | ListMarker::Ordered(OrderedMarker::LowerAlpha {
style: ListDelimiter::Parens,
..
}) | ListMarker::Ordered(OrderedMarker::UpperAlpha {
style: ListDelimiter::Parens,
..
}) | ListMarker::Ordered(OrderedMarker::LowerRoman {
style: ListDelimiter::Parens,
..
}) | ListMarker::Ordered(OrderedMarker::UpperRoman {
style: ListDelimiter::Parens,
..
})
)
{
return None;
}
if indent_cols >= 4 && !ctx.in_list {
return None;
}
let nested_marker = is_content_nested_bullet_marker(
ctx.content,
marker_match.marker_len,
marker_match.spaces_after_bytes,
);
let detection = if ctx.has_blank_before || ctx.at_document_start {
BlockDetectionResult::Yes
} else {
BlockDetectionResult::YesCanInterrupt
};
Some((
detection,
Some(Box::new(ListPrepared {
marker: marker_match.marker,
marker_len: marker_match.marker_len,
spaces_after: marker_match.spaces_after_bytes,
spaces_after_cols: marker_match.spaces_after_cols,
indent_cols,
indent_bytes,
nested_marker,
})),
))
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
_builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let prepared = payload.and_then(|p| p.downcast_ref::<ListPrepared>());
if prepared.is_none() {
return 1;
}
1
}
fn name(&self) -> &'static str {
"list"
}
}
impl BlockParser for BlockQuoteParser {
fn effect(&self) -> BlockEffect {
BlockEffect::OpenBlockQuote
}
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if ctx.blockquote_depth > 0 {
return None;
}
let line = lines.get(line_pos)?;
let (depth, inner_content) = count_blockquote_markers(line);
if depth == 0 {
return None;
}
let marker_info = parse_blockquote_marker_info(line);
let at_document_start = ctx.at_document_start;
let require_blank_before = ctx.config.extensions.blank_before_blockquote;
let can_start = !require_blank_before || can_start_blockquote(line_pos, lines);
let prev_line = lines.get(line_pos.wrapping_sub(1)).unwrap_or(&"");
let prev_line_blank = prev_line.trim().is_empty();
let (prev_depth, prev_inner) = count_blockquote_markers(prev_line);
let prev_line_is_quoted_blank = prev_depth > 0 && prev_inner.trim().is_empty();
let can_nest = if require_blank_before {
depth <= 1 || at_document_start || prev_line_blank || prev_line_is_quoted_blank
} else {
true
};
let has_blank_before = ctx.has_blank_before;
let detection = if has_blank_before || at_document_start {
BlockDetectionResult::Yes
} else {
BlockDetectionResult::YesCanInterrupt
};
Some((
detection,
Some(Box::new(BlockQuotePrepared {
depth,
marker_info,
inner_content: inner_content.to_string(),
can_start,
can_nest,
})),
))
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
let prepared = payload.and_then(|p| p.downcast_ref::<BlockQuotePrepared>());
let Some(prepared) = prepared else {
return 0;
};
let marker_info = &prepared.marker_info;
for level in 0..prepared.depth {
builder.start_node(SyntaxKind::BLOCK_QUOTE.into());
if let Some(info) = marker_info.get(level) {
emit_one_blockquote_marker(builder, info.leading_spaces, info.has_trailing_space);
}
}
0
}
fn name(&self) -> &'static str {
"blockquote"
}
}
impl BlockParser for DefinitionListParser {
fn effect(&self) -> BlockEffect {
BlockEffect::OpenDefinitionList
}
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.definition_lists {
return None;
}
if let Some((marker_char, indent, spaces_after_cols, spaces_after_bytes)) =
try_parse_definition_marker(ctx.content)
{
let indent_bytes =
super::utils::container_stack::byte_index_at_column(ctx.content, indent);
let has_content = ctx
.content
.get(indent_bytes + 1 + spaces_after_bytes..)
.map(|slice| !slice.trim().is_empty())
.unwrap_or(false);
return Some((
BlockDetectionResult::YesCanInterrupt,
Some(Box::new(DefinitionPrepared::Definition {
marker_char,
indent,
spaces_after: spaces_after_bytes,
spaces_after_cols,
has_content,
})),
));
}
if let Some(blank_count) = next_line_is_definition_marker(lines, line_pos)
&& !ctx.content.trim().is_empty()
{
return Some((
BlockDetectionResult::YesCanInterrupt,
Some(Box::new(DefinitionPrepared::Term { blank_count })),
));
}
None
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
_builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let prepared = payload.and_then(|p| p.downcast_ref::<DefinitionPrepared>());
if prepared.is_none() {
return 1;
}
1
}
fn name(&self) -> &'static str {
"definition_list"
}
}
pub(crate) struct FootnoteDefinitionParser;
impl BlockParser for FootnoteDefinitionParser {
fn effect(&self) -> BlockEffect {
BlockEffect::OpenFootnoteDefinition
}
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.footnotes {
return None;
}
let (_id, content_start) = try_parse_footnote_marker(ctx.content)?;
Some((
BlockDetectionResult::YesCanInterrupt,
Some(Box::new(FootnoteDefinitionPrepared { content_start })),
))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
_lines: &[&str],
_line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
let prepared = payload.and_then(|p| p.downcast_ref::<FootnoteDefinitionPrepared>());
let content_start = prepared
.map(|p| p.content_start)
.or_else(|| try_parse_footnote_marker(ctx.content).map(|(_, pos)| pos));
let Some(content_start) = content_start else {
return 1;
};
if let Some(indent_str) = ctx.indent_to_emit {
builder.token(SyntaxKind::WHITESPACE.into(), indent_str);
}
builder.start_node(SyntaxKind::FOOTNOTE_DEFINITION.into());
let marker_text = &ctx.content[..content_start];
if let Some((id, _)) = try_parse_footnote_marker(marker_text) {
builder.token(SyntaxKind::FOOTNOTE_LABEL_START.into(), "[^");
builder.token(SyntaxKind::FOOTNOTE_LABEL_ID.into(), &id);
builder.token(SyntaxKind::FOOTNOTE_LABEL_END.into(), "]");
builder.token(SyntaxKind::FOOTNOTE_LABEL_COLON.into(), ":");
let marker_suffix = marker_text
.strip_prefix("[^")
.and_then(|tail| tail.strip_prefix(id.as_str()))
.and_then(|tail| tail.strip_prefix("]:"))
.unwrap_or("");
if !marker_suffix.is_empty() {
builder.token(SyntaxKind::WHITESPACE.into(), marker_suffix);
}
} else {
builder.token(SyntaxKind::FOOTNOTE_REFERENCE.into(), marker_text);
}
1
}
fn name(&self) -> &'static str {
"footnote_definition"
}
}
impl BlockParser for ReferenceDefinitionParser {
fn effect(&self) -> BlockEffect {
BlockEffect::None
}
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.reference_links {
return None;
}
let _parsed = try_parse_reference_definition(ctx.content)?;
let mut consumed = 1usize;
if ctx.config.extensions.mmd_link_attributes {
let mut i = line_pos + 1;
while i < lines.len() {
let line = lines[i];
if line.trim().is_empty() {
break;
}
if line_is_mmd_link_attribute_continuation(line) {
consumed += 1;
i += 1;
continue;
}
break;
}
}
Some((
BlockDetectionResult::Yes,
Some(Box::new(ReferenceDefinitionPrepared {
consumed_lines: consumed,
})),
))
}
fn parse_prepared(
&self,
_ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
builder.start_node(SyntaxKind::REFERENCE_DEFINITION.into());
let consumed_lines = payload
.and_then(|p| p.downcast_ref::<ReferenceDefinitionPrepared>())
.map(|p| p.consumed_lines)
.unwrap_or(1);
let full_line = lines[line_pos];
let (content_without_newline, line_ending) = strip_newline(full_line);
emit_reference_definition_content(builder, content_without_newline);
if !line_ending.is_empty() {
builder.token(SyntaxKind::NEWLINE.into(), line_ending);
}
for line in lines
.iter()
.skip(line_pos + 1)
.take(consumed_lines.saturating_sub(1))
{
crate::parser::utils::helpers::emit_line_tokens(builder, line);
}
builder.finish_node();
consumed_lines
}
fn name(&self) -> &'static str {
"reference_definition"
}
}
pub(crate) struct TableParser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TableKind {
Grid,
Multiline,
Pipe,
Simple,
}
#[derive(Debug, Clone, Copy)]
struct TablePrepared {
kind: TableKind,
}
impl BlockParser for TableParser {
fn effect(&self) -> BlockEffect {
BlockEffect::None
}
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !(ctx.config.extensions.simple_tables
|| ctx.config.extensions.multiline_tables
|| ctx.config.extensions.grid_tables
|| ctx.config.extensions.pipe_tables)
{
return None;
}
if !ctx.has_blank_before && !ctx.at_document_start {
return None;
}
let mut tmp = GreenNodeBuilder::new();
if ctx.config.extensions.table_captions && is_caption_followed_by_table(lines, line_pos) {
let mut table_pos = line_pos + 1;
while table_pos < lines.len() && !lines[table_pos].trim().is_empty() {
table_pos += 1;
}
if table_pos < lines.len() && lines[table_pos].trim().is_empty() {
table_pos += 1;
}
if ctx.config.extensions.grid_tables
&& try_parse_grid_table(lines, table_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Grid,
})),
));
}
if ctx.config.extensions.multiline_tables
&& try_parse_multiline_table(lines, table_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Multiline,
})),
));
}
if ctx.config.extensions.pipe_tables
&& try_parse_pipe_table(lines, table_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Pipe,
})),
));
}
if ctx.config.extensions.simple_tables
&& try_parse_simple_table(lines, table_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Simple,
})),
));
}
return None;
}
if ctx.config.extensions.grid_tables
&& try_parse_grid_table(lines, line_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Grid,
})),
));
}
if ctx.config.extensions.multiline_tables
&& try_parse_multiline_table(lines, line_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Multiline,
})),
));
}
if ctx.config.extensions.pipe_tables
&& try_parse_pipe_table(lines, line_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Pipe,
})),
));
}
if ctx.config.extensions.simple_tables
&& try_parse_simple_table(lines, line_pos, &mut tmp, ctx.config).is_some()
{
return Some((
BlockDetectionResult::Yes,
Some(Box::new(TablePrepared {
kind: TableKind::Simple,
})),
));
}
None
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let prepared = payload.and_then(|p| p.downcast_ref::<TablePrepared>().copied());
let table_pos = if ctx.config.extensions.table_captions
&& is_caption_followed_by_table(lines, line_pos)
{
let mut pos = line_pos + 1;
while pos < lines.len() && !lines[pos].trim().is_empty() {
pos += 1;
}
if pos < lines.len() && lines[pos].trim().is_empty() {
pos += 1;
}
pos
} else {
line_pos
};
let try_kind =
|kind: TableKind, builder: &mut GreenNodeBuilder<'static>| -> Option<usize> {
match kind {
TableKind::Grid => {
if ctx.config.extensions.grid_tables {
try_parse_grid_table(lines, table_pos, builder, ctx.config)
} else {
None
}
}
TableKind::Multiline => {
if ctx.config.extensions.multiline_tables {
try_parse_multiline_table(lines, table_pos, builder, ctx.config)
} else {
None
}
}
TableKind::Pipe => {
if ctx.config.extensions.pipe_tables {
try_parse_pipe_table(lines, table_pos, builder, ctx.config)
} else {
None
}
}
TableKind::Simple => {
if ctx.config.extensions.simple_tables {
try_parse_simple_table(lines, table_pos, builder, ctx.config)
} else {
None
}
}
}
};
if let Some(prepared) = prepared
&& let Some(n) = try_kind(prepared.kind, builder)
{
return n;
}
if let Some(n) = try_kind(TableKind::Grid, builder) {
return n;
}
if let Some(n) = try_kind(TableKind::Multiline, builder) {
return n;
}
if let Some(n) = try_kind(TableKind::Pipe, builder) {
return n;
}
if let Some(n) = try_kind(TableKind::Simple, builder) {
return n;
}
debug_assert!(false, "TableParser::parse called without a matching table");
1
}
fn name(&self) -> &'static str {
"table"
}
}
fn emit_reference_definition_content(builder: &mut GreenNodeBuilder<'static>, text: &str) {
use crate::syntax::SyntaxKind;
if !text.starts_with('[') {
builder.token(SyntaxKind::TEXT.into(), text);
return;
}
let rest = &text[1..];
if let Some(close_pos) = rest.find(']') {
let label = &rest[..close_pos];
let after_bracket = &rest[close_pos + 1..];
if after_bracket.starts_with(':') {
builder.start_node(SyntaxKind::LINK.into());
builder.start_node(SyntaxKind::LINK_START.into());
builder.token(SyntaxKind::LINK_START.into(), "[");
builder.finish_node();
builder.start_node(SyntaxKind::LINK_TEXT.into());
builder.token(SyntaxKind::TEXT.into(), label);
builder.finish_node();
builder.token(SyntaxKind::TEXT.into(), "]");
builder.finish_node();
builder.token(SyntaxKind::TEXT.into(), after_bracket);
return;
}
}
builder.token(SyntaxKind::TEXT.into(), text);
}
pub(crate) struct FencedCodeBlockParser;
impl BlockParser for FencedCodeBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
let content_to_check = if let Some(list_info) = ctx.list_indent_info {
if list_info.content_col > 0 && !ctx.content.is_empty() {
let idx = byte_index_at_column(ctx.content, list_info.content_col);
&ctx.content[idx..]
} else {
ctx.content
}
} else {
ctx.content
};
let fence = try_parse_fence_open(content_to_check)?;
if (fence.fence_char == '`' && !ctx.config.extensions.backtick_code_blocks)
|| (fence.fence_char == '~' && !ctx.config.extensions.fenced_code_blocks)
{
return None;
}
let trimmed_info = fence.info_string.trim();
if trimmed_info.starts_with('{') && trimmed_info.ends_with('}') {
if trimmed_info.starts_with("{=") {
if !ctx.config.extensions.raw_attribute {
return None;
}
} else if !ctx.config.extensions.fenced_code_attributes {
return None;
}
}
let info = InfoString::parse(&fence.info_string);
let is_executable = matches!(info.block_type, CodeBlockType::Executable { .. });
if is_executable && !ctx.config.extensions.executable_code {
return None;
}
let has_info = !fence.info_string.trim().is_empty();
let has_matching_closer = {
let mut found = false;
let container_content_col = ctx.content_indent
+ ctx
.list_indent_info
.map(|list_info| list_info.content_col)
.unwrap_or(0);
for raw_line in lines.iter().skip(line_pos + 1) {
let (line_bq_depth, inner) = count_blockquote_markers(raw_line);
if line_bq_depth < ctx.blockquote_depth {
break;
}
let candidate = if container_content_col > 0 && !inner.is_empty() {
let idx = byte_index_at_column(inner, container_content_col);
if idx <= inner.len() {
&inner[idx..]
} else {
inner
}
} else {
inner
};
if is_closing_fence(candidate, &fence) {
found = true;
break;
}
}
found
};
if !has_matching_closer {
return None;
}
let next_nonblank_is_command = lines
.iter()
.skip(line_pos + 1)
.find(|l| !l.trim().is_empty())
.is_some_and(|l| l.trim_start().starts_with('%'));
let bare_fence_before_command_with_closer = has_matching_closer && next_nonblank_is_command;
let bare_fence_after_colon_with_closer = has_matching_closer
&& next_nonblank_is_command
&& line_pos > 0
&& lines[line_pos - 1].trim_end().ends_with(':');
let bare_fence_in_list_with_closer = has_matching_closer && ctx.list_indent_info.is_some();
let bare_fence_after_matching_closer = has_matching_closer
&& next_nonblank_is_command
&& line_pos > 0
&& is_closing_fence(lines[line_pos - 1], &fence);
let detection = if has_info
|| bare_fence_before_command_with_closer
|| bare_fence_after_colon_with_closer
|| bare_fence_in_list_with_closer
|| bare_fence_after_matching_closer
{
BlockDetectionResult::YesCanInterrupt
} else if ctx.has_blank_before {
BlockDetectionResult::Yes
} else {
BlockDetectionResult::No
};
match detection {
BlockDetectionResult::No => None,
_ => Some((detection, Some(Box::new(fence)))),
}
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let list_indent_stripped = ctx.list_indent_info.map(|i| i.content_col).unwrap_or(0);
let fence = if let Some(fence) = payload.and_then(|p| p.downcast_ref::<FenceInfo>()) {
fence.clone()
} else {
let content_to_check = if list_indent_stripped > 0 && !ctx.content.is_empty() {
let idx = byte_index_at_column(ctx.content, list_indent_stripped);
&ctx.content[idx..]
} else {
ctx.content
};
try_parse_fence_open(content_to_check).expect("Fence should exist")
};
let total_indent = ctx.content_indent + list_indent_stripped;
let new_pos = if ctx.config.extensions.tex_math_gfm && is_gfm_math_fence(&fence) {
parse_fenced_math_block(
builder,
lines,
line_pos,
fence,
ctx.blockquote_depth,
total_indent,
None,
)
} else {
parse_fenced_code_block(
builder,
lines,
line_pos,
fence,
ctx.blockquote_depth,
total_indent,
None,
)
};
new_pos - line_pos
}
fn name(&self) -> &'static str {
"fenced_code_block"
}
}
pub(crate) struct HtmlBlockParser;
impl BlockParser for HtmlBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.raw_html {
return None;
}
let block_type = try_parse_html_block_start(ctx.content)?;
let detection = if ctx.has_blank_before || ctx.at_document_start {
BlockDetectionResult::Yes
} else {
BlockDetectionResult::YesCanInterrupt
};
Some((detection, Some(Box::new(block_type))))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
let block_type = if let Some(bt) = payload.and_then(|p| p.downcast_ref::<HtmlBlockType>()) {
bt.clone()
} else {
try_parse_html_block_start(ctx.content).expect("HTML block type should exist")
};
let new_pos = parse_html_block(builder, lines, line_pos, block_type, ctx.blockquote_depth);
new_pos - line_pos
}
fn name(&self) -> &'static str {
"html_block"
}
}
pub(crate) struct LatexEnvironmentParser;
impl BlockParser for LatexEnvironmentParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.raw_tex {
return None;
}
let env_name = extract_environment_name(ctx.content)?;
let env_info = LatexEnvInfo { env_name };
use super::blocks::raw_blocks::is_inline_math_environment;
if is_inline_math_environment(&env_info.env_name) {
return None;
}
let detection = if ctx.has_blank_before || ctx.at_document_start {
BlockDetectionResult::Yes
} else {
BlockDetectionResult::YesCanInterrupt
};
Some((detection, Some(Box::new(env_info))))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
let env_info = if let Some(info) = payload.and_then(|p| p.downcast_ref::<LatexEnvInfo>()) {
info.clone()
} else {
let env_name =
extract_environment_name(ctx.content).expect("LaTeX env info should exist");
LatexEnvInfo { env_name }
};
builder.start_node(SyntaxKind::TEX_BLOCK.into());
let mut current_pos = line_pos;
let end_marker = format!("\\end{{{}}}", env_info.env_name);
let mut first_line = true;
while current_pos < lines.len() {
let line = lines[current_pos];
if !first_line {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
first_line = false;
let content = line.trim_end_matches(&['\r', '\n'][..]);
builder.token(SyntaxKind::TEXT.into(), content);
current_pos += 1;
if line.trim_start().starts_with(&end_marker) {
break;
}
}
if current_pos > line_pos {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
builder.finish_node();
current_pos - line_pos
}
fn name(&self) -> &'static str {
"latex_environment"
}
}
pub(crate) struct RawTexBlockParser;
impl BlockParser for RawTexBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.raw_tex {
return None;
}
if !ctx.has_blank_before && !ctx.at_document_start {
return None;
}
if !raw_blocks::can_start_raw_block(ctx.content, ctx.config) {
return None;
}
Some((BlockDetectionResult::Yes, None))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
raw_blocks::parse_raw_tex_block(builder, lines, line_pos, ctx.blockquote_depth)
}
fn name(&self) -> &'static str {
"raw_tex_block"
}
}
pub(crate) struct LineBlockParser;
impl BlockParser for LineBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.line_blocks {
return None;
}
try_parse_line_block_start(ctx.content)?;
let raw_line = lines.get(line_pos)?;
try_parse_line_block_start(raw_line)?;
if !ctx.has_blank_before && !ctx.at_document_start {
return None;
}
let detection = BlockDetectionResult::Yes;
Some((detection, None))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
let new_pos = parse_line_block(lines, line_pos, builder, ctx.config);
new_pos - line_pos
}
fn name(&self) -> &'static str {
"line_block"
}
}
pub(crate) struct FencedDivOpenParser;
fn content_for_fenced_div_detection<'a>(ctx: &BlockContext<'a>) -> &'a str {
if let Some(list_info) = ctx.list_indent_info {
let (indent_cols, _) = leading_indent(ctx.content);
if indent_cols >= list_info.content_col {
let idx = byte_index_at_column(ctx.content, list_info.content_col);
return &ctx.content[idx..];
}
}
ctx.content
}
impl BlockParser for FencedDivOpenParser {
fn effect(&self) -> BlockEffect {
BlockEffect::OpenFencedDiv
}
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.fenced_divs {
return None;
}
let content = content_for_fenced_div_detection(ctx);
let div_fence = try_parse_div_fence_open(content)?;
Some((BlockDetectionResult::Yes, Some(Box::new(div_fence))))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
let div_fence = payload
.and_then(|p| p.downcast_ref::<DivFenceInfo>())
.cloned()
.or_else(|| try_parse_div_fence_open(content_for_fenced_div_detection(ctx)))
.unwrap_or(DivFenceInfo {
attributes: String::new(),
fence_count: 3,
});
builder.start_node(SyntaxKind::FENCED_DIV.into());
builder.start_node(SyntaxKind::DIV_FENCE_OPEN.into());
let full_line = lines[line_pos];
let line_no_bq = strip_n_blockquote_markers(full_line, ctx.blockquote_depth);
let trimmed = line_no_bq.trim_start();
let leading_ws_len = line_no_bq.len() - trimmed.len();
if leading_ws_len > 0 {
builder.token(SyntaxKind::WHITESPACE.into(), &line_no_bq[..leading_ws_len]);
}
let fence_str: String = ":".repeat(div_fence.fence_count);
builder.token(SyntaxKind::TEXT.into(), &fence_str);
let after_colons = &trimmed[div_fence.fence_count..];
let (content_before_newline, newline_str) = strip_newline(after_colons);
if !div_fence.attributes.is_empty() {
let has_leading_space = content_before_newline.starts_with(' ');
if has_leading_space {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
let content_after_space = if has_leading_space {
&content_before_newline[1..]
} else {
content_before_newline
};
builder.start_node(SyntaxKind::DIV_INFO.into());
builder.token(SyntaxKind::TEXT.into(), &div_fence.attributes);
builder.finish_node();
let after_attrs = if div_fence.attributes.starts_with('{') {
if let Some(close_idx) = content_after_space.find('}') {
&content_after_space[close_idx + 1..]
} else {
""
}
} else {
&content_after_space[div_fence.attributes.len()..]
};
if !after_attrs.is_empty() {
let suffix_trimmed = after_attrs.trim_start();
let ws_len = after_attrs.len() - suffix_trimmed.len();
if ws_len > 0 {
builder.token(SyntaxKind::WHITESPACE.into(), &after_attrs[..ws_len]);
}
if !suffix_trimmed.is_empty() {
builder.token(SyntaxKind::TEXT.into(), suffix_trimmed);
}
}
}
if !newline_str.is_empty() {
builder.token(SyntaxKind::NEWLINE.into(), newline_str);
}
builder.finish_node();
1
}
fn name(&self) -> &'static str {
"fenced_div_open"
}
}
pub(crate) struct FencedDivCloseParser;
impl BlockParser for FencedDivCloseParser {
fn effect(&self) -> BlockEffect {
BlockEffect::CloseFencedDiv
}
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.config.extensions.fenced_divs {
return None;
}
if !ctx.in_fenced_div {
return None;
}
if !is_div_closing_fence(content_for_fenced_div_detection(ctx)) {
return None;
}
Some((BlockDetectionResult::YesCanInterrupt, None))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
use crate::syntax::SyntaxKind;
builder.start_node(SyntaxKind::DIV_FENCE_CLOSE.into());
let full_line = lines[line_pos];
let line_no_bq = strip_n_blockquote_markers(full_line, ctx.blockquote_depth);
let trimmed = line_no_bq.trim_start();
let leading_ws_len = line_no_bq.len() - trimmed.len();
if leading_ws_len > 0 {
builder.token(SyntaxKind::WHITESPACE.into(), &line_no_bq[..leading_ws_len]);
}
let (content_without_newline, line_ending) = strip_newline(trimmed);
builder.token(SyntaxKind::TEXT.into(), content_without_newline);
if !line_ending.is_empty() {
builder.token(SyntaxKind::NEWLINE.into(), line_ending);
}
builder.finish_node();
1
}
fn name(&self) -> &'static str {
"fenced_div_close"
}
}
pub(crate) struct IndentedCodeBlockParser;
impl BlockParser for IndentedCodeBlockParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
_lines: &[&str],
_line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
if !ctx.has_blank_before_strict {
return None;
}
let list_content_col = ctx
.list_indent_info
.map(|list_info| list_info.content_col)
.unwrap_or(0);
let required_indent = list_content_col + 4;
let (indent_cols, _) = leading_indent(ctx.content);
if indent_cols < required_indent && try_parse_list_marker(ctx.content, ctx.config).is_some()
{
return None;
}
if indent_cols < required_indent || !is_indented_code_line(ctx.content) {
return None;
}
Some((BlockDetectionResult::Yes, None))
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
let base_indent = ctx.content_indent
+ ctx
.list_indent_info
.map(|list_info| list_info.content_col)
.unwrap_or(0);
let new_pos =
parse_indented_code_block(builder, lines, line_pos, ctx.blockquote_depth, base_indent);
new_pos - line_pos
}
fn name(&self) -> &'static str {
"indented_code_block"
}
}
pub(crate) struct SetextHeadingParser;
impl BlockParser for SetextHeadingParser {
fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<(BlockDetectionResult, Option<Box<dyn Any>>)> {
let follows_setext_heading = if line_pos >= 2 {
let prev_text = count_blockquote_markers(lines[line_pos - 2]).1;
let prev_underline = count_blockquote_markers(lines[line_pos - 1]).1;
try_parse_setext_heading(&[prev_text, prev_underline], 0).is_some()
} else {
false
};
if ctx.config.extensions.blank_before_header
&& !ctx.has_blank_before
&& !ctx.at_document_start
&& !follows_setext_heading
{
return None;
}
let next_line = ctx.next_line?;
let lines = [ctx.content, next_line];
if try_parse_setext_heading(&lines, 0).is_some() {
Some((BlockDetectionResult::Yes, None))
} else {
None
}
}
fn parse_prepared(
&self,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
pos: usize,
_payload: Option<&dyn Any>,
) -> usize {
let text_line = lines[pos];
let underline_line = lines[pos + 1];
let underline_char = underline_line.trim().chars().next().unwrap_or('=');
let level = if underline_char == '=' { 1 } else { 2 };
emit_setext_heading(builder, text_line, underline_line, level, ctx.config);
2
}
fn name(&self) -> &'static str {
"setext_heading"
}
}
pub(crate) struct BlockParserRegistry {
parsers: Vec<Box<dyn BlockParser>>,
}
impl BlockParserRegistry {
pub fn new() -> Self {
let parsers: Vec<Box<dyn BlockParser>> = vec![
Box::new(PandocTitleBlockParser),
Box::new(MmdTitleBlockParser),
Box::new(FencedCodeBlockParser),
Box::new(YamlMetadataParser),
Box::new(ListParser),
Box::new(FencedDivCloseParser),
Box::new(FencedDivOpenParser),
Box::new(SetextHeadingParser),
Box::new(AtxHeadingParser),
Box::new(HtmlBlockParser),
Box::new(TableParser),
Box::new(IndentedCodeBlockParser),
Box::new(LatexEnvironmentParser),
Box::new(RawTexBlockParser),
Box::new(LineBlockParser),
Box::new(BlockQuoteParser),
Box::new(HorizontalRuleParser),
Box::new(FigureParser),
Box::new(DefinitionListParser),
Box::new(FootnoteDefinitionParser),
Box::new(ReferenceDefinitionParser),
];
Self { parsers }
}
pub fn detect_prepared(
&self,
ctx: &BlockContext,
lines: &[&str],
line_pos: usize,
) -> Option<PreparedBlockMatch> {
for (i, parser) in self.parsers.iter().enumerate() {
if let Some((detection, payload)) = parser.detect_prepared(ctx, lines, line_pos) {
log::debug!("Block detected by: {}", parser.name());
return Some(PreparedBlockMatch {
parser_index: i,
detection,
effect: parser.effect(),
payload,
});
}
}
None
}
pub fn parser_name(&self, block_match: &PreparedBlockMatch) -> &'static str {
self.parsers[block_match.parser_index].name()
}
pub fn parse_prepared(
&self,
block_match: &PreparedBlockMatch,
ctx: &BlockContext,
builder: &mut GreenNodeBuilder<'static>,
lines: &[&str],
line_pos: usize,
) -> usize {
let parser = &self.parsers[block_match.parser_index];
log::debug!("Block parsed by: {}", parser.name());
parser.parse_prepared(
ctx,
builder,
lines,
line_pos,
block_match.payload.as_deref(),
)
}
}