use tracing::trace;
use super::{FENCE_RE, is_null_lang};
use crate::{
classify::{ClassifyCtx, LineClass, classify_line},
wrap::FenceTracker,
};
#[derive(Debug, PartialEq, Eq)]
pub(super) enum AttachmentOutcome {
Attached,
Preserved,
}
#[derive(Debug, PartialEq, Eq)]
enum NextFence {
Attachable {
blank_count: usize,
},
NotAttachable {
blank_count: usize,
},
}
fn attach_specifier_to_fence(fence_line: &str, specifier: &str, spec_indent: &str) -> String {
let Some(cap) = FENCE_RE.captures(fence_line) else {
return fence_line.to_owned();
};
let fence_indent = cap.get(1).map_or("", |m| m.as_str());
let fence_marker = cap.get(2).map_or("```", |m| m.as_str());
let final_indent = if fence_indent.is_empty() || spec_indent.starts_with(fence_indent) {
spec_indent
} else {
fence_indent
};
format!("{final_indent}{fence_marker}{specifier}")
}
fn next_attachable_fence<'a, I>(mut lines: std::iter::Peekable<I>) -> NextFence
where
I: Iterator<Item = &'a String>,
{
let mut blank_count = 0;
while let Some(next_line) = lines.peek() {
if next_line.trim().is_empty() {
blank_count += 1;
let _ = lines.next();
continue;
}
let is_attachable = FENCE_RE
.captures(next_line)
.is_some_and(|captures| is_null_lang(captures.get(3).map_or("", |m| m.as_str())));
return if is_attachable {
NextFence::Attachable { blank_count }
} else {
NextFence::NotAttachable { blank_count }
};
}
NextFence::NotAttachable { blank_count }
}
fn is_thematic_break(line: &str) -> bool {
classify_line(line, &ClassifyCtx::default()) == LineClass::ThematicBreak
}
pub(super) fn preserve_thematic_break(line: &str, out: &mut Vec<String>) -> bool {
if is_thematic_break(line) {
trace!(
line_len = line.len(),
"refusing to attach a thematic break to the fence below"
);
out.push(line.to_owned());
return true;
}
false
}
pub(super) fn attach_to_next_fence<'a, I>(
lines: &mut std::iter::Peekable<I>,
specifier: &str,
indent: &str,
out: &mut Vec<String>,
specifier_line: &str,
tracker: &mut FenceTracker,
) -> AttachmentOutcome
where
I: Iterator<Item = &'a String> + Clone,
{
match next_attachable_fence(lines.clone()) {
NextFence::Attachable { blank_count } => {
for _ in 0..blank_count {
let _ = lines.next();
}
if let Some(fence_line) = lines.next() {
out.push(attach_specifier_to_fence(fence_line, specifier, indent));
let _ = tracker.observe_source_line(fence_line);
}
AttachmentOutcome::Attached
}
NextFence::NotAttachable { blank_count } => {
out.push(specifier_line.to_string());
for _ in 0..blank_count {
if let Some(blank_line) = lines.next() {
out.push(blank_line.clone());
}
}
AttachmentOutcome::Preserved
}
}
}