#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FenceTracker {
in_code_block: bool,
code_fence_char: Option<char>,
code_fence_count: usize,
}
impl FenceTracker {
#[must_use]
pub const fn new() -> Self {
Self {
in_code_block: false,
code_fence_char: None,
code_fence_count: 0,
}
}
#[must_use]
pub const fn in_code_block(&self) -> bool {
self.in_code_block
}
#[must_use]
pub fn process_line(&self, line: &str) -> Self {
let trimmed = line.trim_start();
if trimmed.starts_with("```") || trimmed.starts_with("~~~") {
let Some(fence_char) = trimmed.chars().next() else {
return *self;
};
let fence_count =
trimmed.chars().take_while(|&c| c == fence_char).count();
if fence_count >= 3 {
if !self.in_code_block {
return Self {
in_code_block: true,
code_fence_char: Some(fence_char),
code_fence_count: fence_count,
};
} else if self.code_fence_char == Some(fence_char)
&& fence_count >= self.code_fence_count
{
return Self {
in_code_block: false,
code_fence_char: None,
code_fence_count: 0,
};
}
}
}
*self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct InlineTracker {
in_code_block: bool,
in_inline_code: bool,
fence_char: Option<char>,
fence_count: usize,
}
impl InlineTracker {
#[must_use]
pub const fn new() -> Self {
Self {
in_code_block: false,
in_inline_code: false,
fence_char: None,
fence_count: 0,
}
}
#[must_use]
pub const fn in_any_code(&self) -> bool {
self.in_code_block || self.in_inline_code
}
#[must_use]
pub const fn in_code_block(&self) -> bool {
self.in_code_block
}
#[must_use]
pub const fn in_inline_code(&self) -> bool {
self.in_inline_code
}
#[must_use]
pub fn process_backticks<I>(&self, chars: &mut I) -> (Self, usize)
where
I: Iterator<Item = char> + Clone,
{
let mut tick_count = 1; let mut temp_chars = chars.clone();
while temp_chars.next() == Some('`') {
tick_count += 1;
}
for _ in 1..tick_count {
chars.next();
}
if tick_count >= 3 {
if !self.in_code_block {
(
Self {
in_code_block: true,
in_inline_code: false, fence_char: Some('`'),
fence_count: tick_count,
},
tick_count,
)
} else if self.fence_char == Some('`') && tick_count >= self.fence_count {
(
Self {
in_code_block: false,
in_inline_code: false,
fence_char: None,
fence_count: 0,
},
tick_count,
)
} else {
(*self, tick_count)
}
} else if tick_count == 1 && !self.in_code_block {
(
Self {
in_inline_code: !self.in_inline_code,
..*self
},
tick_count,
)
} else {
(*self, tick_count)
}
}
#[must_use]
pub fn process_tildes<I>(&self, chars: &mut I) -> (Self, usize)
where
I: Iterator<Item = char> + Clone,
{
let mut tilde_count = 1; let mut temp_chars = chars.clone();
while temp_chars.next() == Some('~') {
tilde_count += 1;
}
for _ in 1..tilde_count {
chars.next();
}
if tilde_count >= 3 {
if !self.in_code_block {
(
Self {
in_code_block: true,
in_inline_code: false, fence_char: Some('~'),
fence_count: tilde_count,
},
tilde_count,
)
} else if self.fence_char == Some('~') && tilde_count >= self.fence_count
{
(
Self {
in_code_block: false,
in_inline_code: false,
fence_char: None,
fence_count: 0,
},
tilde_count,
)
} else {
(*self, tilde_count)
}
} else {
(*self, tilde_count)
}
}
#[must_use]
pub const fn process_newline(&self) -> Self {
Self {
in_inline_code: false,
..*self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fence_tracker_basic() {
let tracker = FenceTracker::new();
assert!(!tracker.in_code_block());
let tracker = tracker.process_line("```rust");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("fn main() {}");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("```");
assert!(!tracker.in_code_block());
}
#[test]
fn test_fence_tracker_tilde() {
let tracker = FenceTracker::new();
let tracker = tracker.process_line("~~~");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("code");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("~~~");
assert!(!tracker.in_code_block());
}
#[test]
fn test_fence_tracker_mismatched() {
let tracker = FenceTracker::new();
let tracker = tracker.process_line("```");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("~~~");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("```");
assert!(!tracker.in_code_block());
}
#[test]
fn test_fence_tracker_count() {
let tracker = FenceTracker::new();
let tracker = tracker.process_line("````");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("```");
assert!(tracker.in_code_block());
let tracker = tracker.process_line("````");
assert!(!tracker.in_code_block());
}
#[test]
fn test_fence_tracker_indented() {
let tracker = FenceTracker::new();
let tracker = tracker.process_line(" ```");
assert!(tracker.in_code_block());
let tracker = tracker.process_line(" ```");
assert!(!tracker.in_code_block());
}
#[test]
fn test_inline_code_tracker_basic() {
let tracker = InlineTracker::new();
assert!(!tracker.in_any_code());
let mut chars = "rest".chars();
let (tracker, count) = tracker.process_backticks(&mut chars);
assert_eq!(count, 1);
assert!(tracker.in_inline_code());
assert!(tracker.in_any_code());
let mut chars = "rest".chars();
let (tracker, count) = tracker.process_backticks(&mut chars);
assert_eq!(count, 1);
assert!(!tracker.in_inline_code());
assert!(!tracker.in_any_code());
}
#[test]
fn test_inline_code_tracker_fence() {
let tracker = InlineTracker::new();
let mut chars = "``rust".chars();
let (tracker, count) = tracker.process_backticks(&mut chars);
assert_eq!(count, 3);
assert!(tracker.in_code_block());
assert!(!tracker.in_inline_code());
let mut chars = "rest".chars();
let (tracker, _) = tracker.process_backticks(&mut chars);
assert!(tracker.in_code_block());
assert!(!tracker.in_inline_code());
let mut chars = "``".chars();
let (tracker, count) = tracker.process_backticks(&mut chars);
assert_eq!(count, 3);
assert!(!tracker.in_code_block());
assert!(!tracker.in_inline_code());
}
#[test]
fn test_inline_code_tracker_tildes() {
let tracker = InlineTracker::new();
let mut chars = "~~".chars();
let (tracker, count) = tracker.process_tildes(&mut chars);
assert_eq!(count, 3);
assert!(tracker.in_code_block());
let mut chars = "~~".chars();
let (tracker, count) = tracker.process_tildes(&mut chars);
assert_eq!(count, 3);
assert!(!tracker.in_code_block());
}
#[test]
fn test_inline_code_tracker_newline() {
let tracker = InlineTracker::new();
let mut chars = "rest".chars();
let (tracker, _) = tracker.process_backticks(&mut chars);
assert!(tracker.in_inline_code());
let tracker = tracker.process_newline();
assert!(!tracker.in_inline_code());
}
}