markdown-ppp 2.9.2

Feature-rich Markdown Parsing and Pretty-Printing library
Documentation
use crate::ast::*;
use crate::printer::{inline::ToDocInline, ToDoc};
use pretty::{Arena, DocAllocator, DocBuilder};
use std::rc::Rc;

impl<'a> ToDoc<'a> for Vec<Block> {
    fn to_doc(
        &self,
        config: Rc<crate::printer::config::Config>,
        arena: &'a Arena<'a>,
    ) -> DocBuilder<'a, Arena<'a>, ()> {
        let refs: Vec<_> = self.iter().collect();
        refs.to_doc(config, arena)
    }
}

impl<'a> ToDoc<'a> for Vec<&Block> {
    fn to_doc(
        &self,
        config: Rc<crate::printer::config::Config>,
        arena: &'a Arena<'a>,
    ) -> DocBuilder<'a, Arena<'a>, ()> {
        let mut acc = arena.nil();
        for (i, block) in self.iter().enumerate() {
            if i > 0 {
                // first block should not have an empty line before it
                acc = acc.append(arena.hardline());
                if matches!(block, Block::List(_)) {
                    if config.empty_line_before_list {
                        // empty line before list block
                        acc = acc.append(arena.hardline());
                    }
                } else {
                    acc = acc.append(arena.hardline());
                }
            }
            acc = acc.append(block.to_doc(config.clone(), arena))
        }
        acc
    }
}

/// Block-level nodes
impl<'a> ToDoc<'a> for Block {
    fn to_doc(
        &self,
        config: Rc<crate::printer::config::Config>,
        arena: &'a Arena<'a>,
    ) -> DocBuilder<'a, Arena<'a>, ()> {
        match self {
            Block::Paragraph(inlines) => inlines.to_doc_inline(true, arena),
            Block::Heading(v) => v.to_doc(config, arena),
            Block::ThematicBreak => arena.text("---"),
            Block::BlockQuote(inner) => {
                crate::printer::blockquote::blockquote_to_doc(config, arena, inner)
            }
            Block::List(v) => v.to_doc(config, arena),
            Block::CodeBlock(CodeBlock { kind, literal }) => {
                match kind {
                    CodeBlockKind::Fenced { info } => {
                        let info = info.as_deref().unwrap_or("");
                        // Use hardline() between lines so nest() indentation applies correctly
                        // when the code block is inside a list or other nested structure.
                        // We use split('\n') instead of lines() to preserve trailing newlines.
                        let mut doc = arena.text(format!("```{info}"));

                        // Handle code block content.
                        // For non-empty content, we use split('\n') instead of lines() to preserve
                        // trailing newlines. Each line gets a hardline() before it so that nest()
                        // indentation applies correctly when inside lists or other nested structures.
                        // IMPORTANT: For blank lines (empty or whitespace-only), we only add
                        // hardline() without any text, so that nest() doesn't compound whitespace
                        // on repeated format passes. This ensures idempotent formatting.
                        if !literal.is_empty() {
                            let lines: Vec<&str> = literal.split('\n').collect();
                            for line in lines {
                                doc = doc.append(arena.hardline());
                                // Only add text for lines with non-whitespace content.
                                // This prevents whitespace from compounding on each format pass.
                                let trimmed = line.trim_start();
                                if !trimmed.is_empty() {
                                    doc = doc.append(arena.text(line.to_string()));
                                }
                            }
                        }

                        // Closing fence must be on its own line
                        doc.append(arena.hardline()).append(arena.text("```"))
                    }
                    CodeBlockKind::Indented => {
                        // Each line indented with 4 spaces
                        let indented = literal
                            .lines()
                            .map(|l| format!("    {l}"))
                            .collect::<Vec<_>>()
                            .join("\n");
                        arena.text(indented)
                    }
                }
            }
            Block::HtmlBlock(html) => arena.text(html.clone()),
            Block::Definition(def) => arena
                .text("[")
                .append(def.label.to_doc_inline(true, arena))
                .append(arena.text("]: "))
                .append(arena.text(format!(
                    "{}{}",
                    def.destination,
                    def.title
                        .as_ref()
                        .map(|t| format!(" \"{t}\""))
                        .unwrap_or_default()
                ))),

            Block::Empty => arena.nil(),
            Block::Table(v) => v.to_doc(config, arena),
            Block::FootnoteDefinition(def) => arena
                .text(format!("[^{}]: ", def.label))
                .append(def.blocks.to_doc(config, arena)),
            Block::GitHubAlert(alert) => {
                crate::printer::github_alert::github_alert_to_doc(alert, config, arena)
            }
        }
    }
}