use super::marks::{Mark, flags};
pub fn resolve_code_spans(marks: &mut [Mark], text: &[u8], html_spans: &[(u32, u32)]) {
let len = marks.len();
for i in 0..len {
if marks[i].ch != b'`' || marks[i].is_resolved() {
continue;
}
let opener_pos = marks[i].pos as usize;
if pos_in_spans(opener_pos as u32, html_spans) {
continue;
}
if opener_pos > 0 && text[opener_pos - 1] == b'\\' {
let backslash_escaped = opener_pos > 1 && text[opener_pos - 2] == b'\\';
if !backslash_escaped {
continue;
}
}
let opener_len = marks[i].len();
let opener_end = marks[i].end as usize;
for j in (i + 1)..len {
if marks[j].ch != b'`' || marks[j].is_resolved() {
continue;
}
if marks[j].len() == opener_len {
let closer_pos = marks[j].pos as usize;
if closer_pos > 0 && text[closer_pos - 1] == b'\\' {
let backslash_pos = closer_pos - 1;
if backslash_pos >= opener_end {
} else {
let backslash_escaped =
backslash_pos > 0 && text[backslash_pos - 1] == b'\\';
if !backslash_escaped {
continue;
}
}
}
marks[i].resolve();
marks[j].resolve();
for mark in &mut marks[(i + 1)..j] {
mark.flags |= flags::IN_CODE;
}
break;
}
}
}
}
#[inline]
fn pos_in_spans(pos: u32, spans: &[(u32, u32)]) -> bool {
spans.iter().any(|&(start, end)| pos >= start && pos < end)
}
pub fn extract_code_spans(marks: &[Mark]) -> impl Iterator<Item = CodeSpan> + '_ {
let mut i = 0;
std::iter::from_fn(move || {
while i < marks.len() {
let mark = &marks[i];
if mark.ch == b'`' && mark.is_resolved() {
let opener_end = mark.end;
for (j, mark_j) in marks.iter().enumerate().skip(i + 1) {
if mark_j.ch == b'`' && mark_j.is_resolved() && mark_j.len() == mark.len() {
let closer_pos = mark_j.pos;
let result = CodeSpan {
opener_pos: mark.pos,
opener_end,
closer_pos,
closer_end: mark_j.end,
};
i = j + 1;
return Some(result);
}
}
}
i += 1;
}
None
})
}
#[derive(Debug, Clone, Copy)]
pub struct CodeSpan {
pub opener_pos: u32,
pub opener_end: u32,
pub closer_pos: u32,
pub closer_end: u32,
}
impl CodeSpan {
pub fn content_range(&self) -> (u32, u32) {
(self.opener_end, self.closer_pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::inline::marks::{MarkBuffer, collect_marks};
#[test]
fn test_simple_code_span() {
let text = b"hello `code` world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].content_range(), (7, 11)); }
#[test]
fn test_double_backtick() {
let text = b"``code with ` backtick``";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 1);
}
#[test]
fn test_unmatched_backticks() {
let text = b"hello `code`` world";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 0);
}
#[test]
fn test_multiple_code_spans() {
let text = b"`a` and `b`";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 2);
}
#[test]
fn test_emphasis_inside_code() {
let text = b"`*not emphasis*`";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
for mark in buffer.marks() {
if mark.ch == b'*' {
assert!(mark.flags & flags::IN_CODE != 0);
}
}
}
#[test]
fn test_backslash_inside_code_span() {
let text = b"`foo\\`bar`";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].opener_pos, 0);
assert_eq!(spans[0].closer_pos, 5);
}
#[test]
fn test_escaped_backtick_not_opener() {
let text = b"\\`not code`";
let mut buffer = MarkBuffer::new();
collect_marks(text, &mut buffer);
resolve_code_spans(buffer.marks_mut(), text, &[]);
let spans: Vec<_> = extract_code_spans(buffer.marks()).collect();
assert_eq!(spans.len(), 0);
}
}