use crate::options::Constructs;
use crate::syntax::run_len;
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 indent = run_len(line.as_bytes(), b' ');
if indent >= 4 || line.as_bytes().get(indent) == Some(&b'\t') {
return None;
}
let tail = &line[indent..];
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 => 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
}
#[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("[^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 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);
}
}