use tracing::debug;
use crate::{
classify::{ClassifyCtx, LineClass, classify_line_with_body},
ellipsis::replace_ellipsis,
table::reflow_table,
wrap::{LinkReferenceMatcher, classify_block, leading_indent},
};
pub(super) struct TableSubstitutions {
pub(super) ellipsis: bool,
pub(super) code_emphasis: bool,
}
fn opens_table_run(line_class: LineClass, body: &str, is_quoted: bool) -> bool {
if is_quoted {
return false;
}
line_class == LineClass::TableRow
|| (line_class == LineClass::TableDelimiter && body.starts_with('|'))
}
fn is_indented_content_line(line: &str) -> bool {
let (indent_width, first_content_byte) = leading_indent(line);
indent_width >= 4
&& line[first_content_byte..]
.chars()
.any(|c| !c.is_whitespace())
}
pub(super) struct ProcessBuffer {
out: Vec<String>,
table_lines: Vec<bool>,
buf: Vec<String>,
in_table: bool,
ellipsis: bool,
code_emphasis: bool,
}
impl ProcessBuffer {
pub(super) fn new(substitutions: &TableSubstitutions) -> Self {
Self {
out: Vec::new(),
table_lines: Vec::new(),
buf: Vec::new(),
in_table: false,
ellipsis: substitutions.ellipsis,
code_emphasis: substitutions.code_emphasis,
}
}
pub(super) fn push_out(&mut self, line: String) {
self.out.push(line);
self.table_lines.push(false);
}
pub(super) fn finish(mut self) -> (Vec<String>, Vec<bool>) {
self.flush();
(self.out, self.table_lines)
}
pub(super) fn flush(&mut self) {
debug!(
in_table = self.in_table,
lines = self.buf.len(),
"ProcessBuffer::flush"
);
if self.buf.is_empty() {
return;
}
let buffered = std::mem::take(&mut self.buf);
if self.in_table {
if !crate::table::is_valid_table(&buffered) {
self.table_lines
.extend(std::iter::repeat_n(false, buffered.len()));
self.out.extend(buffered);
self.in_table = false;
return;
}
let table_lines = if self.code_emphasis {
crate::code_emphasis::fix_code_emphasis(&buffered)
} else {
buffered
};
let table_lines = if self.ellipsis {
replace_ellipsis(&table_lines)
} else {
table_lines
};
let table_lines = reflow_table(&table_lines);
self.table_lines
.extend(std::iter::repeat_n(true, table_lines.len()));
self.out.extend(table_lines);
} else {
self.table_lines
.extend(std::iter::repeat_n(false, buffered.len()));
self.out.extend(buffered);
}
self.in_table = false;
}
pub(super) fn push_verbatim(&mut self, line: &str) {
self.flush();
self.push_out(line.to_string());
}
pub(super) fn handle_fence_line(&mut self, line: &str, is_fence_marker: bool) -> bool {
if !is_fence_marker {
return false;
}
self.push_verbatim(line);
true
}
pub(super) fn handle_table_line(&mut self, line: String) -> Option<String> {
let classified = classify_line_with_body(&line, &ClassifyCtx::default());
let prefix = &line[..line.len() - classified.body.len()];
let is_quoted = prefix.contains('>');
let line_class = classified.class;
if opens_table_run(line_class, classified.body, is_quoted) {
debug!(
line_len = line.len(),
buffered_lines = self.buf.len(),
"ProcessBuffer: table-mode on"
);
self.in_table = true;
self.buf.push(line);
return None;
}
if !self.in_table {
return Some(line);
}
if line.trim().is_empty() {
self.flush();
return Some(line);
}
if classify_block(&line, LinkReferenceMatcher::production()).is_some() {
debug!(
line_len = line.len(),
in_table = self.in_table,
buffered_lines = self.buf.len(),
"ProcessBuffer: flushing on block boundary"
);
self.flush();
return Some(line);
}
if is_indented_content_line(&line) {
self.flush();
return Some(line);
}
if line.contains('|') || line_class == LineClass::TableDelimiter {
self.buf.push(line);
return None;
}
self.flush();
Some(line)
}
}
#[cfg(test)]
mod tests;