use crate::classify::{ClassifyCtx, LineClass, classify_line_with_body, list_content_indent};
#[derive(Default)]
pub(super) struct ItemStack {
columns: Vec<usize>,
prev_text: bool,
}
pub(super) struct Placement {
pub(super) container: usize,
pub(super) relative: String,
}
impl ItemStack {
pub(super) fn close_outdented(&mut self, line: &str) {
let indent = leading_width(line);
while self.columns.last().is_some_and(|&column| column > indent) {
self.columns.pop();
}
self.prev_text = false;
}
pub(super) fn blank(&mut self) { self.prev_text = false; }
pub(super) fn place(&mut self, line: &str) -> Placement {
let indent = leading_width(line);
let text = line.trim_start();
let lazy = self.prev_text && is_paragraph_text(text);
if !lazy {
while self.columns.last().is_some_and(|&column| column > indent) {
self.columns.pop();
}
}
let container = self.columns.last().copied().unwrap_or(0);
let relative = format!("{}{text}", " ".repeat(indent.saturating_sub(container)));
let class = classify_line_with_body(&relative, &ClassifyCtx::default()).class;
if class == LineClass::ListItem {
self.columns.push(list_content_indent(text, indent));
}
self.prev_text = match class {
LineClass::ParagraphText => true,
LineClass::ListItem => is_paragraph_text(item_content(text)),
_ => false,
};
Placement {
container,
relative,
}
}
}
fn item_content(text: &str) -> &str {
let after_marker = text.trim_start_matches(|ch: char| !matches!(ch, ' ' | '\t'));
after_marker.trim_start_matches([' ', '\t'])
}
fn is_paragraph_text(text: &str) -> bool {
classify_line_with_body(text, &ClassifyCtx::default()).class == LineClass::ParagraphText
}
fn leading_width(line: &str) -> usize {
line.chars()
.take_while(|ch| ch.is_whitespace())
.fold(0, |column, ch| {
if ch == '\t' {
column + 4 - column % 4
} else {
column + 1
}
})
}