use alloc::vec::Vec;
use crate::ast::{BlankLine, Block, HtmlContainerContent, NodeMeta};
use crate::span::Span;
pub(super) fn add_blank_lines(input: &str, blocks: &mut Vec<Block>) {
let lines = Lines::new(input);
lines.fill(blocks, Span::new(0, input.len()), Edges::BOTH);
}
#[derive(Clone, Copy)]
struct Edges {
leading: Edge,
trailing: Edge,
}
#[derive(Clone, Copy, Eq, PartialEq)]
enum Edge {
Each,
AfterOne,
None,
}
impl Edges {
const BOTH: Self = Self {
leading: Edge::Each,
trailing: Edge::Each,
};
const HTML: Self = Self {
leading: Edge::Each,
trailing: Edge::AfterOne,
};
const NONE: Self = Self {
leading: Edge::None,
trailing: Edge::None,
};
}
struct Lines<'a> {
input: &'a str,
starts: Vec<usize>,
}
impl<'a> Lines<'a> {
fn new(input: &'a str) -> Self {
let bytes = input.as_bytes();
let mut starts = alloc::vec![0];
let mut index = 0;
while index < bytes.len() {
match bytes[index] {
b'\n' => starts.push(index + 1),
b'\r' if bytes.get(index + 1) == Some(&b'\n') => {
index += 1;
starts.push(index + 1);
}
b'\r' => starts.push(index + 1),
_ => {}
}
index += 1;
}
if starts.last() != Some(&input.len()) {
starts.push(input.len());
}
Self { input, starts }
}
fn blank_in(&self, from: usize, to: usize) -> Vec<Span> {
let first = self.starts.partition_point(|&start| start < from);
let mut blank = Vec::new();
for pair in self.starts[first..].windows(2) {
let [start, end] = [pair[0], pair[1]];
if end > to {
break;
}
let line = &self.input[start..end];
let line = match start {
0 => line.strip_prefix('\u{feff}').unwrap_or(line),
_ => line,
};
if !line.is_empty() && is_blank(line) {
blank.push(Span::new(start, end));
}
}
blank
}
fn rest_of_line(&self, from: usize, to: usize) -> Option<Span> {
let next = self.starts.partition_point(|&start| start <= from);
let end = *self.starts.get(next)?;
let starts_a_line = next > 0 && self.starts[next - 1] == from;
(!starts_a_line && end <= to && from < end).then(|| Span::new(from, end))
}
fn fill(&self, blocks: &mut Vec<Block>, region: Span, edges: Edges) {
for block in blocks.iter_mut() {
self.fill_block(block);
}
let spans: Vec<Span> = blocks.iter().filter_map(Block::span).collect();
if spans.len() != blocks.len() {
return;
}
let mut gaps: Vec<(usize, Vec<Span>)> = Vec::new();
let mut from = region.start;
let head = self
.rest_of_line(from, spans.first().map_or(region.end, |span| span.start))
.filter(|rest| is_blank(&self.input[rest.start..rest.end]));
if let Some(rest) = head {
from = rest.end;
}
for (index, span) in spans.iter().enumerate() {
let mut blank = self.blank_in(from, span.start);
if index == 0 {
blank.splice(0..0, head);
}
let edge = if index == 0 {
edges.leading
} else {
Edge::AfterOne
};
keep(&mut blank, edge);
if !blank.is_empty() {
gaps.push((index, blank));
}
from = span.end;
}
let mut blank = self.blank_in(from, region.end);
if spans.is_empty() {
blank.splice(0..0, head);
}
keep(
&mut blank,
if spans.is_empty() {
match edges.leading {
Edge::None => Edge::None,
_ => Edge::Each,
}
} else {
edges.trailing
},
);
if !blank.is_empty() {
gaps.push((spans.len(), blank));
}
if gaps.is_empty() {
return;
}
let count = blocks.len() + gaps.iter().map(|(_, blank)| blank.len()).sum::<usize>();
let nodes = core::mem::replace(blocks, Vec::with_capacity(count));
let mut gaps = gaps.into_iter().peekable();
for (index, block) in nodes.into_iter().enumerate() {
if let Some((_, blank)) = gaps.next_if(|(at, _)| *at == index) {
blocks.extend(blank.into_iter().map(blank_line));
}
blocks.push(block);
}
if let Some((_, blank)) = gaps.next() {
blocks.extend(blank.into_iter().map(blank_line));
}
}
fn fill_block(&self, block: &mut Block) {
match block {
Block::BlockQuote(node) => {
let region = node.meta.span;
self.fill_container(&mut node.children, region, Edges::BOTH);
}
Block::Alert(node) => {
let region = node.meta.span;
self.fill_container(&mut node.children, region, Edges::BOTH);
}
Block::ContainerDirective(node) => {
let region = node.meta.span;
self.fill_container(&mut node.children, region, Edges::BOTH);
}
Block::HtmlContainer(node) => {
let region = node.meta.span;
if let HtmlContainerContent::Blocks(children) = &mut node.content {
self.fill_container(children, region, Edges::HTML);
}
}
Block::FootnoteDefinition(node) => {
let region = node.meta.span;
self.fill_container(&mut node.children, region, Edges::NONE);
}
Block::List(list) => {
for item in &mut list.children {
let region = item.meta.span;
self.fill_container(&mut item.children, region, Edges::NONE);
}
for index in 1..list.children.len() {
let (Some(end), Some(start)) = (
list.children[index - 1].meta.span.map(|span| span.end),
list.children[index].meta.span.map(|span| span.start),
) else {
continue;
};
let earlier = &mut list.children[index - 1];
if earlier.children.is_empty() {
continue;
}
let mut blank = self.blank_in(end, start);
keep(&mut blank, Edge::AfterOne);
if let (Some(span), Some(last)) = (&mut earlier.meta.span, blank.last()) {
span.end = last.end;
}
earlier.children.extend(blank.into_iter().map(blank_line));
}
}
_ => {}
}
}
fn fill_container(&self, blocks: &mut Vec<Block>, region: Option<Span>, edges: Edges) {
if let Some(region) = region {
self.fill(blocks, region, edges);
}
}
}
fn keep(blank: &mut Vec<Span>, edge: Edge) {
match edge {
Edge::Each => {}
Edge::AfterOne => {
if !blank.is_empty() {
blank.drain(..1);
}
}
Edge::None => blank.clear(),
}
}
fn is_blank(line: &str) -> bool {
line.bytes()
.all(|byte| matches!(byte, b' ' | b'\t' | b'>' | b'\n' | b'\r'))
}
fn blank_line(span: Span) -> Block {
Block::BlankLine(BlankLine {
meta: NodeMeta::new(Some(span)),
})
}