pub(crate) const FENCE_LANG: &str = "lashlang";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FenceSpan {
pub(crate) open_start: usize,
pub(crate) opener_len: usize,
pub(crate) body_start: usize,
pub(crate) body_end: usize,
pub(crate) close_len: usize,
}
impl FenceSpan {
pub(crate) fn is_closed(&self) -> bool {
self.close_len > 0
}
}
pub(crate) fn first_lashlang_fence_span(text: &str) -> Option<FenceSpan> {
let bytes = text.as_bytes();
let mut search_from = 0usize;
while search_from <= bytes.len() {
let rel = text.get(search_from..)?.find("```")?;
let open_start = search_from + rel;
let opener_len = bytes[open_start..]
.iter()
.take_while(|&&b| b == b'`')
.count();
let after_open = open_start + opener_len;
let rest = &text[after_open..];
let lang_end = rest.find('\n').unwrap_or(rest.len());
let lang = rest[..lang_end].trim();
if lang != FENCE_LANG {
search_from = after_open;
continue;
}
let body_start = if lang_end < rest.len() {
after_open + lang_end + 1
} else {
after_open + lang_end
};
let (body_end, close_len) = match find_closing_fence(&bytes[body_start..], opener_len) {
Some((rel, _run_len)) => (body_start + rel, opener_len),
None => (text.len(), 0),
};
return Some(FenceSpan {
open_start,
opener_len,
body_start,
body_end,
close_len,
});
}
None
}
pub(crate) fn find_closing_fence(bytes: &[u8], min_len: usize) -> Option<(usize, usize)> {
if min_len == 0 {
return None;
}
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'`' {
let start = i;
while i < bytes.len() && bytes[i] == b'`' {
i += 1;
}
let len = i - start;
if len >= min_len {
return Some((start, len));
}
} else {
i += 1;
}
}
None
}
pub(crate) fn body_has_closing_fence(body: &str, opener_len: usize) -> bool {
find_closing_fence(body.as_bytes(), opener_len).is_some()
}