use crate::options::Constructs;
use crate::syntax;
use super::cursor::Cursor;
use super::probe::{Start, probe};
use super::scan;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum LineStart {
Blockquote,
SetextUnderline,
ThematicBreak,
AtxHeading,
CodeFence,
HtmlBlock,
MathFence,
Liquid,
DirectiveOpener,
DirectiveCloser,
FootnoteDefinition,
ListItem,
TableDelimiterRow,
TableRow,
}
pub fn ordered_number(marker: &str) -> Option<u32> {
marker.trim_end_matches(['.', ')']).parse().ok()
}
pub fn line_start(constructs: &Constructs, line: &str, in_paragraph: bool) -> Option<LineStart> {
let tail = flow_tail(line)?;
if let Some(start) = probe(constructs, tail, in_paragraph) {
return Some(match start {
Start::Quote => LineStart::Blockquote,
Start::Setext(_) => LineStart::SetextUnderline,
Start::ThematicBreak => LineStart::ThematicBreak,
Start::Atx { .. } => LineStart::AtxHeading,
Start::Fence { .. } => LineStart::CodeFence,
Start::Html { .. } => LineStart::HtmlBlock,
Start::MathFence { .. } => LineStart::MathFence,
Start::Liquid => {
let closer = syntax::liquid::open(tail)?;
if let Some(after) = syntax::liquid::close(&tail[2..], closer)
&& !scan::is_blank(&tail[2 + after..])
{
return None;
}
LineStart::Liquid
}
Start::Directive { .. } => LineStart::DirectiveOpener,
Start::FootnoteDef { .. } => LineStart::FootnoteDefinition,
Start::ListItem { .. } => LineStart::ListItem,
Start::TableDelimiter(_) => LineStart::TableDelimiterRow,
});
}
if constructs.container_directive && scan::fence_close(tail, b':', 3) {
return Some(LineStart::DirectiveCloser);
}
if constructs.gfm_table && tail.starts_with('|') {
return Some(LineStart::TableRow);
}
None
}
pub fn table_delimiter_activates(constructs: &Constructs, header: &str, row: &str) -> bool {
flow_tail(header).is_some()
&& matches!(
flow_tail(row).and_then(|tail| probe(constructs, tail, true)),
Some(Start::TableDelimiter(align)) if scan::table_row_cells(header).len() == align.len()
)
}
fn flow_tail(line: &str) -> Option<&str> {
Cursor::new(line, 0).flow_start().map(|cursor| cursor.tail())
}
#[cfg(test)]
mod tests {
use super::*;
fn md(line: &str, in_paragraph: bool) -> Option<LineStart> {
line_start(&Constructs::markdown(), line, in_paragraph)
}
#[test]
fn probe_table_mirrors_engine() {
assert_eq!(md("> q", false), Some(LineStart::Blockquote));
assert_eq!(md("---", true), Some(LineStart::SetextUnderline));
assert_eq!(md("---", false), Some(LineStart::ThematicBreak));
assert_eq!(md("# h", false), Some(LineStart::AtxHeading));
assert_eq!(md("```rs", false), Some(LineStart::CodeFence));
assert_eq!(md("<div>", false), Some(LineStart::HtmlBlock));
assert_eq!(md("$$", false), Some(LineStart::MathFence));
assert_eq!(md("{% t %}", false), Some(LineStart::Liquid));
assert_eq!(md("{% t", false), Some(LineStart::Liquid));
assert_eq!(md("{{ t }} text", false), None);
assert_eq!(md("[^1]: n", false), Some(LineStart::FootnoteDefinition));
assert_eq!(md("- item", false), Some(LineStart::ListItem));
assert_eq!(md("plain text", true), None);
}
#[test]
fn ordered_numbers() {
assert_eq!(ordered_number("007."), Some(7));
assert_eq!(ordered_number("999999999)"), Some(999_999_999));
assert_eq!(ordered_number("-"), None);
}
#[test]
fn covers_prettiers_documented_misses() {
assert_eq!(md(":::note", false), Some(LineStart::DirectiveOpener));
assert_eq!(md(":::", false), Some(LineStart::DirectiveCloser));
assert_eq!(md(":::)", false), None, "an emoticon is paragraph text");
assert_eq!(md("| - | - |", true), Some(LineStart::TableDelimiterRow));
assert_eq!(md("| a | b |", false), Some(LineStart::TableRow));
}
#[test]
fn table_delimiter_activates_on_matching_cells() {
let md = Constructs::markdown();
assert!(table_delimiter_activates(&md, "| a | b |", "| - | - |"));
assert!(table_delimiter_activates(&md, "a | b", "--- | ---"));
assert!(
!table_delimiter_activates(&md, "| a | b |", "| - | - | - |"),
"a cell count mismatch"
);
assert!(!table_delimiter_activates(&md, "| a | b |", "| a | b |"), "not a delimiter row");
assert!(
!table_delimiter_activates(&md, " | a | b |", "| - | - |"),
"an indented header"
);
}
#[test]
fn indent_is_indented_code() {
assert_eq!(md(" # not a heading", false), None);
assert_eq!(md("\t- not a list", false), None);
assert_eq!(md(" \t- not a list", false), None);
assert_eq!(md(" - list", false), Some(LineStart::ListItem));
}
#[test]
fn construct_gating() {
let mut c = Constructs::markdown();
c.container_directive = false;
c.gfm_table = false;
assert_eq!(line_start(&c, ":::", false), None);
assert_eq!(line_start(&c, "| a |", false), None);
}
}