use regex::Regex;
pub(super) static FENCE_RE: std::sync::LazyLock<Regex> =
std::sync::LazyLock::new(|| {
Regex::new(r"^(\s*)(`{3,}|~{3,})([^\r\n]*)$").expect("valid fence regex")
});
#[doc(hidden)]
#[must_use]
#[rustfmt::skip]
pub fn is_fence(line: &str) -> Option<(&str, &str, &str)> {
FENCE_RE.captures(line).map(|cap| {
let indent = cap.get(1).map_or("", |m| m.as_str());
let fence = cap.get(2).map_or("", |m| m.as_str());
let info = cap.get(3).map_or("", |m| m.as_str());
(indent, fence, info)
})
}
pub(crate) fn handle_fence_line(
out: &mut Vec<String>,
buf: &mut Vec<(String, bool)>,
indent: &mut String,
width: usize,
line: &str,
tracker: &mut FenceTracker,
) -> bool {
if !tracker.observe(line) {
return false;
}
super::flush_paragraph(out, buf, indent, width);
buf.clear();
indent.clear();
out.push(line.to_string());
true
}
#[derive(Default, Debug)]
pub struct FenceTracker {
state: Option<(char, usize)>,
}
impl FenceTracker {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn observe(&mut self, line: &str) -> bool {
let Some((_indent, fence, _info)) = is_fence(line) else {
return false;
};
let mut chars = fence.chars();
let marker_ch = chars.next().expect("FENCE_RE guarantees a non-empty fence");
let marker_len = chars.count() + 1;
match self.state {
Some((open_ch, open_len)) if marker_ch == open_ch && marker_len >= open_len => {
self.state = None;
}
Some(_) => {}
None => {
self.state = Some((marker_ch, marker_len));
}
}
true
}
#[must_use]
pub fn in_fence(&self) -> bool {
self.state.is_some()
}
}