use std::ops::Range;
use crate::ast::TableAlign;
use crate::options::Constructs;
use crate::syntax;
use super::scan;
pub enum Start {
Quote,
Setext(u8),
ThematicBreak,
Atx {
level: u8,
content: Range<usize>,
},
Fence {
fence: u8,
len: u32,
info: Range<usize>,
},
Html {
kind: u8,
},
ListItem {
m: scan::ListMarkerScan,
empty: bool,
},
TableDelimiter(Vec<TableAlign>),
FootnoteDef {
label: Range<usize>,
content: usize,
},
MathFence {
len: u32,
meta: Range<usize>,
},
Liquid,
Directive {
len: u32,
},
}
impl Start {
pub(super) fn is_container(&self) -> bool {
match self {
Start::Quote | Start::FootnoteDef { .. } => true,
Start::ListItem { m, empty } => item_interrupts(m, *empty),
_ => false,
}
}
}
pub fn probe(constructs: &Constructs, tail: &str, para_open: bool) -> Option<Start> {
if tail.starts_with('>') {
return Some(Start::Quote);
}
if para_open && let Some(level) = scan::setext_underline(tail) {
return Some(Start::Setext(level));
}
if scan::thematic_break(tail) {
return Some(Start::ThematicBreak);
}
if let Some((level, content)) = scan::atx_heading(tail) {
return Some(Start::Atx { level, content });
}
if let Some((fence, len, info)) = scan::fence_open(tail) {
return Some(Start::Fence { fence, len, info });
}
if constructs.html_flow
&& let Some(kind) = scan::html_block_start(tail, para_open)
{
return Some(Start::Html { kind });
}
if constructs.math_flow
&& let Some((len, meta)) = scan::math_fence_open(tail)
{
return Some(Start::MathFence { len, meta });
}
if constructs.liquid && syntax::liquid::open(tail).is_some() {
return Some(Start::Liquid);
}
if constructs.container_directive
&& let Some(len) = scan::directive_fence_open(tail)
{
return Some(Start::Directive { len });
}
if constructs.gfm_footnote
&& let Some((label, content)) = scan::footnote_definition_start(tail)
{
return Some(Start::FootnoteDef { label, content });
}
if let Some(m) = scan::list_marker(tail) {
let empty = scan::is_blank(&tail[m.len..]);
return Some(Start::ListItem { m, empty });
}
if para_open
&& constructs.gfm_table
&& let Some(align) = scan::table_delimiter_row(tail)
{
return Some(Start::TableDelimiter(align));
}
None
}
pub(super) fn item_interrupts(m: &scan::ListMarkerScan, empty: bool) -> bool {
!empty && (!m.ordered || m.starts_at_one)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toggles_gate_their_rows() {
let on = Constructs::markdown();
let off = Constructs {
html_flow: false,
math_flow: false,
liquid: false,
container_directive: false,
gfm_footnote: false,
gfm_table: false,
..on
};
for (tail, para_open) in [
("<div>", false),
("$$", false),
("{% t %}", false),
(":::a", false),
("[^1]: n", false),
("| - |", true),
] {
assert!(probe(&on, tail, para_open).is_some(), "{tail}");
assert!(probe(&off, tail, para_open).is_none(), "{tail}");
}
assert!(matches!(probe(&off, "> q", false), Some(Start::Quote)), "core stays on");
}
#[test]
fn table_delimiter_needs_a_paragraph() {
assert!(probe(&Constructs::markdown(), "| - |", false).is_none());
}
}