Skip to main content

markdown_syntax/
serialize.rs

1//! AST to Markdown. The verbs live on [`Document`]
2//! ([`to_markdown`](Document::to_markdown) /
3//! [`to_markdown_with`](Document::to_markdown_with)); [`SerializeOptions`] tunes
4//! the output style. The document is validated first, so serialization can fail
5//! with a [`SerializeError`].
6//!
7//! A document with its source is written partly from it (`reuse`, decision
8//! 0017): each node that corresponds to the node its source parses to is
9//! written as its source, an edited one with its own syntax copied around
10//! what changed, and the rest is rendered. Rendering only renders: each node
11//! is written in the spelling the AST records for it, or else by one fixed
12//! rule, with text, escapes, and character references as recorded. The output
13//! is parsed back only to check the blocks written partly from the source.
14
15use alloc::{
16    format,
17    string::{String, ToString},
18    vec::Vec,
19};
20
21use alloc::collections::BTreeMap;
22
23use crate::{
24    ast::*, diagnostic::Diagnostic, leaves, parse::Definitions, parse::InlineSource, span::Span,
25    validate::validate_document,
26};
27
28mod reuse;
29
30/// The newline style emitted by the serializer.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum LineEnding {
33    /// Unix `\n`.
34    Lf,
35    /// Windows `\r\n`.
36    CrLf,
37}
38
39impl LineEnding {
40    fn as_str(self) -> &'static str {
41        match self {
42            Self::Lf => "\n",
43            Self::CrLf => "\r\n",
44        }
45    }
46}
47
48/// The bullet char [`SerializeOptions::bullet`] writes unordered lists with.
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum BulletMarker {
51    /// `-`.
52    Dash,
53    /// `*`.
54    Asterisk,
55    /// `+`.
56    Plus,
57}
58
59/// The delimiter [`SerializeOptions::ordered_delimiter`] writes ordered-list
60/// markers with.
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub enum OrderedDelimiter {
63    /// `.`, as in `1.`.
64    Period,
65    /// `)`, as in `1)`.
66    Paren,
67}
68
69/// Output-style options for serialization. Defaults: LF, trailing newline, and
70/// the list markers and code fences the AST records.
71#[derive(Clone, Debug, Eq, PartialEq)]
72#[non_exhaustive]
73pub struct SerializeOptions {
74    /// Newline style to emit.
75    pub line_ending: LineEnding,
76    /// Whether to end the output with a trailing newline.
77    pub final_newline: bool,
78    /// The bullet for unordered lists: `None`, the default, keeps the marker
79    /// each list records; `Some` writes every unordered list with it.
80    pub bullet: Option<BulletMarker>,
81    /// The delimiter for ordered-list markers: `None`, the default, keeps the
82    /// delimiter each list records; `Some` writes every ordered list with it.
83    pub ordered_delimiter: Option<OrderedDelimiter>,
84    /// The fence char for fenced code blocks: `None`, the default, keeps the
85    /// fence each block records; `Some` writes every fenced block with it,
86    /// except that an info string holding a backtick always takes tildes.
87    pub fence_marker: Option<FenceMarker>,
88}
89
90impl Default for SerializeOptions {
91    fn default() -> Self {
92        Self {
93            line_ending: LineEnding::Lf,
94            final_newline: true,
95            bullet: None,
96            ordered_delimiter: None,
97            fence_marker: None,
98        }
99    }
100}
101
102/// Why serialization failed.
103#[derive(Clone, Debug, Eq, PartialEq)]
104pub enum SerializeError {
105    /// The AST failed validation; carries the validation diagnostics.
106    InvalidDocument(Vec<Diagnostic>),
107}
108
109impl Document {
110    /// Write this document as Markdown with default options: the source of
111    /// every unchanged node, the source of its own syntax around a node whose
112    /// children changed, and canonical Markdown for the rest.
113    pub fn to_markdown(&self) -> Result<String, SerializeError> {
114        self.to_markdown_with(&SerializeOptions::default())
115    }
116
117    /// Write this document as Markdown with explicit options. A document
118    /// with a source reuses it for each node that corresponds to the node
119    /// the source parses to at the same span, and for the own syntax of one
120    /// whose children changed; the options apply to the rest.
121    pub fn to_markdown_with(&self, options: &SerializeOptions) -> Result<String, SerializeError> {
122        Ok(self.write(options)?.0)
123    }
124
125    /// The text `to_markdown_with` writes, and how many top-level blocks it
126    /// wrote canonically because, written partly from the source, they read
127    /// back as something else.
128    pub(crate) fn write(
129        &self,
130        options: &SerializeOptions,
131    ) -> Result<(String, usize), SerializeError> {
132        let diagnostics = validate_document(self);
133        if !diagnostics.is_empty() {
134            return Err(SerializeError::InvalidDocument(diagnostics));
135        }
136        // A leaf keeps its text only while its inlines are the ones the text
137        // reads as.
138        let current;
139        let definitions = Definitions::of(self);
140        let blocks = if leaves::any_kept(&self.children) {
141            let mut blocks = self.children.clone();
142            leaves::visit_mut(&mut blocks, &mut |leaf| {
143                if let Some(text) = leaf.source.as_deref() {
144                    if crate::parse::parse_inline_tree(text, &definitions).inlines != *leaf.inlines
145                    {
146                        *leaf.source = None;
147                    }
148                }
149            });
150            current = blocks;
151            &current
152        } else {
153            &self.children
154        };
155        let Some(source) = self.source() else {
156            return Ok((write_canonical(blocks, options)?, 0));
157        };
158        // A block written partly from the source stays so only where the
159        // output reads back as it at its place.
160        let base = crate::parse(source).document;
161        let write =
162            |whole: &[bool]| write_reusing(blocks, &base, source, options, &definitions, whole);
163        // A `---` break kept opening the document stays only while no later
164        // line closes it as frontmatter, which every write is checked for.
165        let opens_with_break = matches!(blocks.first(), Some(Block::ThematicBreak(_)));
166        let mut whole = alloc::vec![false; blocks.len()];
167        let (mut output, mut partly) = write(&whole)?;
168        if opens_with_break && opens_frontmatter(&output) {
169            whole[0] = true;
170            (output, partly) = write(&whole)?;
171        }
172        if partly.is_empty() {
173            return Ok((output, 0));
174        }
175        let misread = misread(blocks, &output, &partly);
176        if misread.is_empty() {
177            return Ok((output, 0));
178        }
179        for index in &misread {
180            whole[*index] = true;
181        }
182        output = write(&whole)?.0;
183        if opens_with_break && !whole[0] && opens_frontmatter(&output) {
184            whole[0] = true;
185            output = write(&whole)?.0;
186        }
187        Ok((output, misread.len()))
188    }
189}
190
191/// Whether `output` opens with frontmatter: its first line is a fence that
192/// a later line closes.
193fn opens_frontmatter(output: &str) -> bool {
194    let mut lines = output.split(['\n', '\r']);
195    let opening = lines.next().and_then(crate::parse::frontmatter_fence_kind);
196    opening.is_some() && lines.any(|line| crate::parse::frontmatter_fence_kind(line) == opening)
197}
198
199/// The canonical Markdown of a document's top-level blocks.
200fn write_canonical(blocks: &[Block], options: &SerializeOptions) -> Result<String, SerializeError> {
201    let mut output = write_blocks(blocks, options, Join::Gap, true, None)?;
202    if options.line_ending == LineEnding::CrLf {
203        output = lf_to_crlf(&output);
204    }
205    // Blocks end without a line ending, except an HTML block whose last
206    // line is empty (the final newline ends that line too) and an indented
207    // code block that keeps its value's `\r` or `\r\n` ending.
208    // A last blank line always takes its line ending.
209    let ends_with_blank_line = matches!(blocks.last(), Some(Block::BlankLine(_)));
210    if options.final_newline
211        && !blocks.is_empty()
212        && (ends_with_blank_line || !ends_with_carriage_return_ending(&output))
213    {
214        output.push_str(options.line_ending.as_str());
215    }
216    Ok(output)
217}
218
219/// Writes `blocks` against `base_document`, the parse of `source`: each
220/// block matched to the base block at its span as its source when it
221/// corresponds to it, partly from the source when only what it holds
222/// changed (`reuse`), and canonically otherwise; returns the output and the
223/// indices of the blocks written partly from the source, which `whole`
224/// writes canonically instead. Between two blocks written from the source
225/// and adjacent there the source's own bytes are kept where they read the
226/// same (`reuse::keeps_gap`), as are the bytes before the source's first
227/// block and after its last when those blocks are written from it there;
228/// every other boundary is a blank line.
229///
230/// A block is written canonically instead where its source would read
231/// differently in its new place: one a blank line and a paragraph after it
232/// would change, as they extend an unclosed fence, when it comes before a
233/// block other than its successor in the source; one the block before it
234/// would take in (`moves_into_previous`); and a thematic break that comes to
235/// open the document, where it would open frontmatter.
236fn write_reusing(
237    blocks: &[Block],
238    base_document: &Document,
239    source: &str,
240    options: &SerializeOptions,
241    definitions: &Definitions,
242    whole: &[bool],
243) -> Result<(String, Vec<usize>), SerializeError> {
244    let base = &base_document.children;
245    let mut correspondence = Correspondence {
246        base: base_document,
247        sources: None,
248    };
249    let base_definitions = Definitions::of(base_document);
250    let mut reuse = reuse::Reuse {
251        source,
252        options,
253        correspondence: &mut correspondence,
254        definitions,
255        base_definitions: &base_definitions,
256    };
257    let span_of = |block: &Block| block.span().expect("a reused block has a span");
258    let mut outs = Vec::with_capacity(blocks.len());
259    // Whether each block written partly from the source is an open list.
260    let mut open_lists = alloc::vec![false; blocks.len()];
261    for (index, block) in blocks.iter().enumerate() {
262        let found = block.span().and_then(|span| {
263            let at = base
264                .binary_search_by_key(&span.start, |base| {
265                    base.span().map_or(usize::MAX, |span| span.start)
266                })
267                .ok()?;
268            (base[at].span() == Some(span)
269                && core::mem::discriminant(&base[at]) == core::mem::discriminant(block))
270            .then_some(at)
271        });
272        outs.push(match found {
273            Some(at) => match reuse.block(block, &base[at])? {
274                reuse::Out::Kept(_) => (Some(at), None),
275                reuse::Out::Lines(_) | reuse::Out::OpenList(_) if whole[index] => (None, None),
276                reuse::Out::Lines(lines) => (None, Some((at, lines))),
277                reuse::Out::OpenList(lines) => {
278                    open_lists[index] = true;
279                    (None, Some((at, lines)))
280                }
281                reuse::Out::Render => (None, None),
282            },
283            None => (None, None),
284        });
285    }
286    let mut reused: Vec<Option<usize>> = outs.iter().map(|(at, _)| *at).collect();
287    // The blocks written partly from the source, with their place in it.
288    let mut partly: Vec<Option<(usize, Vec<reuse::Line>)>> =
289        outs.into_iter().map(|(_, lines)| lines).collect();
290    // Where each block written from the source, whole or partly, stands in
291    // the source.
292    let placed =
293        |reused: &[Option<usize>], partly: &[Option<(usize, Vec<reuse::Line>)>], index: usize| {
294            reused[index].or(partly[index].as_ref().map(|(at, _)| *at))
295        };
296    if blocks.len() == base.len()
297        && reused
298            .iter()
299            .enumerate()
300            .all(|(index, at)| *at == Some(index))
301    {
302        return Ok((source.into(), Vec::new()));
303    }
304    // Whether the boundary before each block holds the blank line that
305    // separates two blocks; the source between two blocks adjacent in the
306    // source is kept only where it still does or does not.
307    let separated = separated_boundaries(blocks);
308    let base_separated = separated_boundaries(base);
309    // Whether the source between the block at `index`, placed at `at`, and
310    // the one before it, placed at `at - 1`, is kept: the boundary holds a
311    // separating blank line exactly when it did, and the source reads the
312    // same between them (`reuse::keeps_gap`).
313    let keeps_gap = |partly: &[Option<(usize, Vec<reuse::Line>)>], index: usize, at: usize| {
314        if index == 0 || at == 0 || separated[index] != base_separated[at] {
315            return false;
316        }
317        let left = base[at - 1].span().expect("a parsed block has a span");
318        let right = base[at].span().expect("a parsed block has a span");
319        let left_end = left.start + strip_line_ending(&source[left.start..left.end]).len();
320        reuse::keeps_gap(
321            source,
322            &source[left_end..right.start],
323            (
324                &blocks[index - 1],
325                left,
326                partly[index - 1]
327                    .as_ref()
328                    .map(|(_, lines)| lines.as_slice()),
329            ),
330            (
331                &blocks[index],
332                right,
333                partly[index].as_ref().map(|(_, lines)| lines.as_slice()),
334            ),
335            open_lists[index - 1],
336            true,
337        )
338    };
339    // Backward, so that each block knows whether its successor stays reused,
340    // with the source between them.
341    for index in (0..blocks.len().saturating_sub(1)).rev() {
342        let Some(at) = placed(&reused, &partly, index) else {
343            continue;
344        };
345        if placed(&reused, &partly, index + 1) == Some(at + 1)
346            && keeps_gap(&partly, index + 1, at + 1)
347        {
348            continue;
349        }
350        match &partly[index] {
351            Some((_, lines)) => {
352                let mut text = reuse::join(lines, "\n");
353                text.push('\n');
354                if !closed_by_a_blank_line(&text) {
355                    partly[index] = None;
356                }
357            }
358            None => {
359                let span = span_of(&blocks[index]);
360                if !closed_by_a_blank_line(&source[span.start..span.end]) {
361                    reused[index] = None;
362                }
363            }
364        }
365    }
366
367    let mut output = String::with_capacity(source.len());
368    // The block written before the current one: its index in the source's
369    // parse, where its text ends there, and its line ending there when it was
370    // reused, and its marker when it is a list.
371    let mut previous_at: Option<usize> = None;
372    let mut placement = reuse::Placement::default();
373    let mut previous_ending = "";
374    let mut previous_body_end = 0;
375    // Whether the output ends with the source's own trailing bytes, which
376    // take the place of the final newline.
377    let mut ends_with_source_tail = false;
378    // Whether every block since the last one other than a blank line follows
379    // its predecessor in the source.
380    let mut run_follows = true;
381    for (index, block) in blocks.iter().enumerate() {
382        // A list written partly from the source keeps its recorded marker,
383        // which a list before it may already use.
384        if let (Block::List(list), Some(_)) = (block, &partly[index]) {
385            if placement.previous_list() == Some(written_marker(list)) {
386                partly[index] = None;
387            }
388        }
389        let follows_predecessor = matches!(
390            (previous_at, placed(&reused, &partly, index)),
391            (Some(previous), Some(at)) if previous + 1 == at && keeps_gap(&partly, index, at)
392        );
393        // Blank lines between them do not end a list or a footnote
394        // definition before an indented line, so a block follows its
395        // predecessor only where every block since the last other than a
396        // blank line did.
397        let follows_run = follows_predecessor && run_follows;
398        run_follows = index == 0 || !matches!(block, Block::BlankLine(_)) || follows_run;
399        // A block whose source the block before it could take in stays only
400        // after its predecessor in the source, written as it was there.
401        let taken_in = |text: &str| placement.takes_in(block, text, follows_run);
402        if let Some((_, lines)) = &partly[index] {
403            if taken_in(&lines[0].body) {
404                partly[index] = None;
405            }
406        }
407        let mut at = reused[index];
408        if let Some(position) = at {
409            let start = span_of(block).start;
410            // A break that comes to open the document could open
411            // frontmatter; one that opened it there stays a break while no
412            // later line closes it as frontmatter (`Document::write`), which
413            // `whole` marks.
414            let rewritten = matches!(block, Block::ThematicBreak(_))
415                && index == 0
416                && (position != 0 || whole[0] && source[start..].starts_with("---"))
417                || taken_in(&source[start..]);
418            if rewritten {
419                at = None;
420            }
421        }
422        if index > 0 {
423            if follows_predecessor {
424                let start = span_of(block).start;
425                output.push_str(&source[previous_body_end..start]);
426            } else if separated[index] {
427                // A reused block keeps its own line ending; one more makes
428                // the blank line.
429                output.push_str(previous_ending);
430                match previous_ending {
431                    "" => push_gap(&mut output, options),
432                    "\r" => output.push('\r'),
433                    _ => output.push_str(options.line_ending.as_str()),
434                }
435            } else {
436                // A blank line is a line of its own, without a separator.
437                output.push_str(match previous_ending {
438                    "" => options.line_ending.as_str(),
439                    own => own,
440                });
441            }
442        }
443        previous_ending = "";
444        match at {
445            Some(position) => {
446                let span = span_of(block);
447                let body = strip_line_ending(&source[span.start..span.end]);
448                let body_end = span.start + body.len();
449                if index == 0 && position == 0 {
450                    output.push_str(&source[..span.start]);
451                }
452                output.push_str(body);
453                if index + 1 == blocks.len() && position + 1 == base.len() {
454                    output.push_str(&source[body_end..]);
455                    ends_with_source_tail = true;
456                }
457                previous_ending = &source[body_end..span.end];
458                previous_body_end = body_end;
459                let marker = match block {
460                    Block::List(list) => Some(written_marker(list)),
461                    _ => None,
462                };
463                placement.wrote(block, marker, false);
464            }
465            None if partly[index].is_some() => {
466                let (position, lines) = partly[index].as_ref().expect("written partly");
467                let span = base[*position].span().expect("a parsed block has a span");
468                let body_end = span.start + strip_line_ending(&source[span.start..span.end]).len();
469                if index == 0 && *position == 0 {
470                    output.push_str(&source[..span.start]);
471                }
472                output.push_str(&reuse::join(lines, options.line_ending.as_str()));
473                if index + 1 == blocks.len() && *position + 1 == base.len() {
474                    output.push_str(&source[body_end..]);
475                    ends_with_source_tail = true;
476                }
477                previous_body_end = body_end;
478                let marker = match block {
479                    Block::List(list) => Some(written_marker(list)),
480                    _ => None,
481                };
482                placement.wrote(block, marker, open_lists[index]);
483                at = Some(*position);
484            }
485            None => {
486                let mut text = write_blocks(
487                    core::slice::from_ref(block),
488                    options,
489                    Join::Gap,
490                    index == 0,
491                    placement.previous_list(),
492                )?;
493                if options.line_ending == LineEnding::CrLf {
494                    text = lf_to_crlf(&text);
495                }
496                output.push_str(&text);
497                let marker = match block {
498                    Block::List(list) => {
499                        Some(list_marker(list, options, placement.previous_list()))
500                    }
501                    _ => None,
502                };
503                placement.wrote(block, marker, false);
504            }
505        }
506        previous_at = at;
507    }
508    let ends_with_blank_line = matches!(blocks.last(), Some(Block::BlankLine(_)));
509    if !ends_with_source_tail
510        && options.final_newline
511        && !blocks.is_empty()
512        && (ends_with_blank_line || !ends_with_carriage_return_ending(&output))
513    {
514        // A reused last block keeps its own line ending, which can be part
515        // of its value, as in indented code.
516        output.push_str(match previous_ending {
517            "" => options.line_ending.as_str(),
518            own => own,
519        });
520    }
521    let partly = partly
522        .iter()
523        .enumerate()
524        .filter_map(|(index, lines)| lines.as_ref().map(|_| index))
525        .collect();
526    Ok((output, partly))
527}
528
529/// The blocks among `partly`, indices of `blocks` written partly from the
530/// source, that `output` reads back as something else in their place: each
531/// one that differs, or every one when the output holds a different number
532/// of top-level blocks.
533fn misread(blocks: &[Block], output: &str, partly: &[usize]) -> Vec<usize> {
534    let read = crate::parse(output).document.children;
535    if read.len() != blocks.len() {
536        return partly.to_vec();
537    }
538    partly
539        .iter()
540        .copied()
541        .filter(|index| !reads_as(&read[*index], &blocks[*index]))
542        .collect()
543}
544
545/// Whether `read`, a block of the parsed output, reads as `block` as
546/// "Round-trip stability" compares them, an ordered item without a number
547/// reading as the number it is written with.
548fn reads_as(read: &Block, block: &Block) -> bool {
549    let mut read = crate::compare::normalized_blocks(core::slice::from_ref(read));
550    let mut block = crate::compare::normalized_blocks(core::slice::from_ref(block));
551    number_items(&mut read);
552    number_items(&mut block);
553    read == block
554}
555
556/// Gives each ordered item without a number the one it is written with.
557fn number_items(blocks: &mut [Block]) {
558    for block in blocks {
559        match block {
560            Block::List(list) => {
561                let mut previous = None;
562                for item in &mut list.children {
563                    if list.ordered {
564                        item.number = Some(item_number(item, &mut previous));
565                    }
566                    number_items(&mut item.children);
567                }
568            }
569            Block::BlockQuote(node) => number_items(&mut node.children),
570            Block::Alert(node) => number_items(&mut node.children),
571            Block::FootnoteDefinition(node) => number_items(&mut node.children),
572            Block::ContainerDirective(node) => number_items(&mut node.children),
573            Block::HtmlContainer(node) => {
574                if let HtmlContainerContent::Blocks(children) = &mut node.content {
575                    number_items(children);
576                }
577            }
578            Block::Paragraph(_)
579            | Block::Heading(_)
580            | Block::ThematicBreak(_)
581            | Block::CodeBlock(_)
582            | Block::HtmlBlock(_)
583            | Block::Definition(_)
584            | Block::Table(_)
585            | Block::MathBlock(_)
586            | Block::Frontmatter(_)
587            | Block::LeafDirective(_)
588            | Block::BlankLine(_) => {}
589        }
590    }
591}
592
593/// Whether a block corresponds to a block of the document its source parses
594/// to: their spans and own fields are equal and their children correspond,
595/// where a leaf that keeps its text corresponds to the leaf at its span whose
596/// inline source is that text.
597struct Correspondence<'a> {
598    base: &'a Document,
599    /// The inline source of each leaf of `base`, by span, read once the
600    /// first leaf that keeps its text needs it.
601    sources: Option<BTreeMap<(usize, usize), InlineSource>>,
602}
603
604impl Correspondence<'_> {
605    /// The inline source of each leaf of the base, by span.
606    fn sources(&mut self) -> &BTreeMap<(usize, usize), InlineSource> {
607        let base_document = self.base;
608        self.sources.get_or_insert_with(|| {
609            let mut sources = BTreeMap::new();
610            base_document.each_leaf_source(|_, span, source| {
611                sources.insert((span.start, span.end), source);
612            });
613            sources
614        })
615    }
616
617    /// The inline source of the base leaf at `span`.
618    fn source_of(&mut self, span: Span) -> Option<&InlineSource> {
619        self.sources().get(&(span.start, span.end))
620    }
621
622    fn holds(&mut self, block: &Block, base: &Block) -> bool {
623        if !leaves::any_kept(core::slice::from_ref(block)) {
624            return block == base;
625        }
626        let sources = self.sources();
627        // Each leaf whose text is its base leaf's inline source takes the
628        // inlines that text reads as there.
629        let mut block = block.clone();
630        leaves::visit_mut(core::slice::from_mut(&mut block), &mut |leaf| {
631            let (Some(text), Some(span)) = (leaf.source.as_deref(), leaf.span) else {
632                return;
633            };
634            if let Some(source) = sources
635                .get(&(span.start, span.end))
636                .filter(|source| source.text == text)
637            {
638                crate::parse::inline_spans_to_source(source, leaf.inlines);
639                *leaf.source = None;
640            }
641        });
642        block == *base
643    }
644}
645
646/// For each block, whether the boundary before it holds the blank line that
647/// separates two blocks: one written after a block other than a blank line,
648/// before a block or before the blank lines that precede one.
649fn separated_boundaries(blocks: &[Block]) -> Vec<bool> {
650    let mut separated = alloc::vec![false; blocks.len()];
651    let mut block_follows = false;
652    for index in (1..blocks.len()).rev() {
653        block_follows |= !matches!(blocks[index], Block::BlankLine(_));
654        separated[index] = block_follows && !matches!(blocks[index - 1], Block::BlankLine(_));
655    }
656    separated
657}
658
659/// Whether a block written from the source, whose text opens with `text`,
660/// reads differently where it does not follow its predecessor in the
661/// source, after `last`, the last block written before it other than a blank
662/// line, and `previous_list`, the marker of a list written just before it:
663/// indentation after a list or a footnote definition, which blank lines do
664/// not end, continues them; four columns of it on a block other than
665/// indented code read as indented code, as a paragraph opened by definitions
666/// took them as a continuation; and a list after a list written with its
667/// marker continues that list. A blank line reads as one wherever it stands.
668/// `rewritten_after_an_edit` in `tests/support/edits.rs` states the same rule
669/// for the simulated edits.
670fn moves_into_previous(
671    block: &Block,
672    text: &str,
673    last: Option<&Block>,
674    previous_list: Option<char>,
675) -> bool {
676    match block {
677        Block::BlankLine(_) => false,
678        Block::List(list) if previous_list == Some(written_marker(list)) => true,
679        _ if matches!(last, Some(Block::List(_) | Block::FootnoteDefinition(_))) => {
680            text.starts_with([' ', '\t'])
681        }
682        Block::CodeBlock(_) => false,
683        _ => opens_with_code_indent(text),
684    }
685}
686
687/// Whether `text` opens with four columns of indentation, which starts
688/// indented code where no paragraph continues.
689fn opens_with_code_indent(text: &str) -> bool {
690    let mut column = 0;
691    for char in text.chars() {
692        match char {
693            ' ' => column += 1,
694            '\t' => column += 4 - column % 4,
695            _ => break,
696        }
697        if column >= 4 {
698            return true;
699        }
700    }
701    false
702}
703
704/// Whether the block parsed from `text`, its source with its line ending,
705/// keeps that text when a blank line and a paragraph follow it, as a closed
706/// fence does and an unclosed one does not. `tests/support/edits.rs` holds a
707/// copy.
708fn closed_by_a_blank_line(text: &str) -> bool {
709    let gap = if text.ends_with('\n') {
710        "\n"
711    } else if text.ends_with('\r') {
712        "\r"
713    } else {
714        "\n\n"
715    };
716    let probe = format!("{text}{gap}x");
717    let children = crate::parse(&probe).document.children;
718    let Some((Block::Paragraph(last), rest)) = children.split_last() else {
719        return false;
720    };
721    last.meta
722        .span
723        .is_some_and(|span| span.start + 1 == probe.len())
724        && rest.iter().all(|block| {
725            matches!(block, Block::BlankLine(_))
726                || block.span().is_some_and(|span| span.end <= text.len())
727        })
728}
729
730/// Pushes the gap between two top-level blocks in the options' line ending.
731fn push_gap(output: &mut String, options: &SerializeOptions) {
732    let from = output.len();
733    push_block_gap(output);
734    if options.line_ending == LineEnding::CrLf {
735        let gap = lf_to_crlf(&output[from..]);
736        output.truncate(from);
737        output.push_str(&gap);
738    }
739}
740
741/// `text` without one trailing `\n`, `\r\n`, or `\r`.
742fn strip_line_ending(text: &str) -> &str {
743    text.strip_suffix("\r\n")
744        .or_else(|| text.strip_suffix('\n'))
745        .or_else(|| text.strip_suffix('\r'))
746        .unwrap_or(text)
747}
748
749/// Rewrites each bare `\n` as `\r\n`; existing `\r\n` and `\r` endings that
750/// verbatim values carry stay as they are.
751fn lf_to_crlf(input: &str) -> String {
752    let mut output = String::with_capacity(input.len());
753    let mut previous = '\0';
754    for ch in input.chars() {
755        if ch == '\n' && previous != '\r' {
756            output.push('\r');
757        }
758        output.push(ch);
759        previous = ch;
760    }
761    output
762}
763
764fn ends_with_carriage_return_ending(input: &str) -> bool {
765    input.ends_with('\r') || input.ends_with("\r\n")
766}
767
768/// Separates two blocks with one blank line. A block that already ends its
769/// last line with `\r` or `\r\n` gets only the blank line, written so that it
770/// cannot merge into that ending.
771fn push_block_gap(output: &mut String) {
772    if output.ends_with('\r') {
773        output.push('\r');
774    } else if output.ends_with("\r\n") {
775        output.push('\n');
776    } else {
777        output.push_str("\n\n");
778    }
779}
780
781/// How sibling blocks are separated.
782#[derive(Clone, Copy, Eq, PartialEq)]
783enum Join {
784    /// By a blank line.
785    Gap,
786    /// By a line ending alone, as the blocks of a tight list item are.
787    Line,
788}
789
790/// Writes a sequence of sibling blocks. `document_start` is true only for the
791/// top-level document body, where the first block sits at byte 0 and a
792/// contiguous `---` would open frontmatter.
793///
794/// Two blocks are separated by `join`, and each `BlankLine` is one more empty
795/// line: between two blocks after their separator, and before the first
796/// block or after the last on its own.
797fn write_blocks(
798    blocks: &[Block],
799    options: &SerializeOptions,
800    join: Join,
801    document_start: bool,
802    previous_list: Option<char>,
803) -> Result<String, SerializeError> {
804    let mut output = String::new();
805    // The marker of the list written right before the current block; ordered
806    // and unordered markers never share a char.
807    let mut previous_list = previous_list;
808    // Whether a block other than a blank line was written, and the blank
809    // lines since the last one.
810    let mut wrote_block = false;
811    let mut blank_lines = 0;
812    for (index, block) in blocks.iter().enumerate() {
813        if let Block::BlankLine(_) = block {
814            blank_lines += 1;
815            continue;
816        }
817        if wrote_block {
818            match join {
819                Join::Gap => push_block_gap(&mut output),
820                Join::Line => output.push('\n'),
821            }
822        }
823        output.extend(core::iter::repeat_n('\n', blank_lines));
824        blank_lines = 0;
825        let after_paragraph_line =
826            join == Join::Line && index > 0 && matches!(blocks[index - 1], Block::Paragraph(_));
827        let written = match block {
828            Block::List(list) => {
829                let marker = list_marker(list, options, previous_list);
830                previous_list = Some(marker);
831                write_list(list, options, marker)?
832            }
833            Block::ThematicBreak(node) => {
834                previous_list = None;
835                write_thematic_break(node, document_start && index == 0, after_paragraph_line)
836            }
837            _ => {
838                previous_list = None;
839                write_block(block, options)?
840            }
841        };
842        output.push_str(&written);
843        wrote_block = true;
844    }
845    // Trailing blank lines each end the line before them; with no block,
846    // the first of them starts the output.
847    let trailing = if wrote_block {
848        blank_lines
849    } else {
850        blank_lines.saturating_sub(1)
851    };
852    output.extend(core::iter::repeat_n('\n', trailing));
853    Ok(output)
854}
855
856/// Writes `block` as canonical Markdown, and a leaf that keeps its text as
857/// that text: `to_markdown_with` has cleared every kept text its leaf's
858/// inlines no longer read as.
859fn write_block(block: &Block, options: &SerializeOptions) -> Result<String, SerializeError> {
860    match block {
861        Block::BlankLine(_) => Ok(String::new()),
862        Block::Paragraph(node) => match &node.source {
863            Some(text) => Ok(text.clone()),
864            None => write_inlines(&node.children, Context::BLOCK),
865        },
866        Block::Heading(node) => write_heading(node),
867        Block::ThematicBreak(node) => Ok(write_thematic_break(node, false, false)),
868        Block::BlockQuote(node) => {
869            let inner = write_blocks(&node.children, options, Join::Gap, false, None)?;
870            // No content, or one blank line, is one `>` line.
871            Ok(if inner.is_empty() {
872                ">".into()
873            } else {
874                quote_lines(&inner)
875            })
876        }
877        Block::Alert(node) => write_alert(node, options),
878        Block::List(node) => write_list(node, options, list_marker(node, options, None)),
879        Block::CodeBlock(node) => Ok(write_code_block(node, options)),
880        // An HTML block's lines are joined with `\n`, so a value ending in one
881        // ends with an empty line that belongs to the block (an unclosed
882        // comment, say); it is written as it is.
883        Block::HtmlBlock(node) => Ok(node.value.clone()),
884        Block::HtmlContainer(node) => write_html_container(node, options),
885        Block::Definition(node) => Ok(write_definition(node)),
886        Block::FootnoteDefinition(node) => {
887            let inner = write_blocks(&node.children, options, Join::Gap, false, None)?;
888            // Content opening with a space or a tab (indented code, say)
889            // starts on the line after the label, as in a list item, where
890            // the label's line would take that whitespace as its separator.
891            Ok(if inner.starts_with([' ', '\t']) {
892                format!(
893                    "[^{}]:\n{}",
894                    node.label,
895                    prefix_container_lines(&inner, "    ")
896                )
897            } else {
898                format!("[^{}]: {}", node.label, indent_continuation(&inner))
899            })
900        }
901        Block::Table(node) => write_table(node),
902        Block::MathBlock(node) => {
903            let fence = block_math_fence(&node.value);
904            Ok(fenced_body(&fence, &node.value, &fence))
905        }
906        Block::Frontmatter(node) => {
907            let fence = match node.kind {
908                FrontmatterKind::Yaml => "---",
909                FrontmatterKind::Toml => "+++",
910            };
911            // The value holds the lines between the fences joined with `\n`,
912            // so a final `\n` is an empty last line.
913            Ok(format!("{fence}\n{}\n{fence}", node.value))
914        }
915        Block::LeafDirective(node) => Ok(format!(
916            "::{}{}{}",
917            node.name,
918            write_directive_label(&node.label, node.label_source.as_deref())?,
919            write_attributes(&node.attributes)
920        )),
921        Block::ContainerDirective(node) => {
922            let mut inner = write_blocks(&node.children, options, Join::Gap, false, None)?;
923            let fence = directive_fence(&inner);
924            // Content ends with a line ending before the closing fence; an
925            // empty directive takes no blank line, which would loosen a list
926            // holding it.
927            if !node.children.is_empty() {
928                inner.push('\n');
929            }
930            Ok(format!(
931                "{fence}{}{}{}\n{inner}{fence}",
932                node.name,
933                write_directive_label(&node.label, node.label_source.as_deref())?,
934                write_attributes(&node.attributes)
935            ))
936        }
937    }
938}
939
940/// A thematic break. A dash break is written spaced where a contiguous `---`
941/// would read otherwise: at the document start, where it opens frontmatter,
942/// and on the line after a paragraph's, where it underlines a setext heading.
943fn write_thematic_break(
944    node: &ThematicBreak,
945    at_document_start: bool,
946    after_paragraph_line: bool,
947) -> String {
948    match node.marker {
949        ThematicBreakMarker::Dash if at_document_start || after_paragraph_line => "- - -".into(),
950        ThematicBreakMarker::Dash => "---".into(),
951        ThematicBreakMarker::Asterisk => "***".into(),
952        ThematicBreakMarker::Underscore => "___".into(),
953    }
954}
955
956/// Whether `node` is written as a setext heading: a setext underline can only
957/// express depth 1 (`=`) or 2 (`-`), and it underlines content.
958pub(crate) fn writes_setext(node: &Heading) -> bool {
959    node.kind == HeadingKind::Setext && matches!(node.depth, 1 | 2) && !node.children.is_empty()
960}
961
962fn write_heading(node: &Heading) -> Result<String, SerializeError> {
963    let setext = writes_setext(node);
964    let content = match one_line(node.source.as_deref(), !setext) {
965        Some(text) => text.into(),
966        None => write_inlines(&node.children, Context::HEADING)?,
967    };
968    if setext {
969        let marker = if node.depth == 1 { "=" } else { "-" };
970        return Ok(format!(
971            "{content}\n{}",
972            marker.repeat(content.len().max(3))
973        ));
974    }
975    let hashes = "#".repeat(usize::from(node.depth));
976    Ok(if content.is_empty() {
977        hashes
978    } else if reads_as_closing_sequence(&content) {
979        // Content ending in what would read as the closing sequence keeps it
980        // by a closing sequence of its own.
981        format!("{hashes} {content} #")
982    } else {
983        format!("{hashes} {content}")
984    })
985}
986
987/// Whether written ATX content ends in a `#` run that is all of it or follows
988/// a space or a tab, which a reader strips as the closing sequence.
989fn reads_as_closing_sequence(content: &str) -> bool {
990    let before = content.trim_end_matches('#');
991    before.len() < content.len() && (before.is_empty() || before.ends_with([' ', '\t']))
992}
993
994fn write_definition(node: &Definition) -> String {
995    let destination = write_destination(&node.destination, node.destination_kind);
996    let mut output = format!("[{}]: {}", node.label, destination);
997    if let Some(title) = &node.title {
998        output.push(' ');
999        output.push_str(&write_title(title));
1000    }
1001    output
1002}
1003
1004fn write_html_container(
1005    node: &HtmlContainer,
1006    options: &SerializeOptions,
1007) -> Result<String, SerializeError> {
1008    match &node.content {
1009        HtmlContainerContent::Blocks(children) => {
1010            let inner = write_blocks(children, options, Join::Gap, false, None)?;
1011            // Content takes a blank line before the closing tag; blank lines
1012            // alone stand between the tags.
1013            let blank_lines = children
1014                .iter()
1015                .all(|block| matches!(block, Block::BlankLine(_)));
1016            Ok(if blank_lines {
1017                format!(
1018                    "{}\n{}{}",
1019                    node.opening.raw,
1020                    "\n".repeat(children.len()),
1021                    node.closing.raw
1022                )
1023            } else {
1024                format!("{}\n{}\n\n{}", node.opening.raw, inner, node.closing.raw)
1025            })
1026        }
1027        HtmlContainerContent::Inlines(children) => Ok(format!(
1028            "{}{}{}",
1029            node.opening.raw,
1030            write_inlines(children, Context::BLOCK)?,
1031            node.closing.raw
1032        )),
1033    }
1034}
1035
1036fn write_alert(node: &Alert, options: &SerializeOptions) -> Result<String, SerializeError> {
1037    let mut output = String::from("> [!");
1038    output.push_str(alert_kind_name(node.kind));
1039    output.push(']');
1040    if let Some(title) = &node.title {
1041        if !title.is_empty() {
1042            output.push(' ');
1043            // A line ending would end the marker's line.
1044            output.push_str(&title.replace(['\n', '\r'], " "));
1045        }
1046    }
1047    let inner = write_blocks(&node.children, options, Join::Gap, false, None)?;
1048    if !inner.is_empty() {
1049        output.push('\n');
1050        output.push_str(&quote_lines(&inner));
1051    } else if !node.children.is_empty() {
1052        // One blank line.
1053        output.push_str("\n>");
1054    }
1055    Ok(output)
1056}
1057
1058/// `inner` with every line in the quote: an empty last line, which a value
1059/// ending in a line ending holds, takes `>` too.
1060fn quote_lines(inner: &str) -> String {
1061    let mut output = prefix_container_lines(inner, "> ");
1062    if inner.ends_with(['\n', '\r']) {
1063        output.push('>');
1064    }
1065    output
1066}
1067
1068fn alert_kind_name(kind: AlertKind) -> &'static str {
1069    match kind {
1070        AlertKind::Note => "NOTE",
1071        AlertKind::Tip => "TIP",
1072        AlertKind::Important => "IMPORTANT",
1073        AlertKind::Warning => "WARNING",
1074        AlertKind::Caution => "CAUTION",
1075    }
1076}
1077
1078/// The marker char a list is written with: the one the options replace it
1079/// with, or else the one the AST records. A marker that the list written right
1080/// before it in the same container, `previous`, also uses yields to the next
1081/// one in the order `-`, `*`, `+`, or `.`, `)`; validation keeps two adjacent
1082/// recorded markers apart, so only a replaced marker can meet this.
1083fn list_marker(list: &List, options: &SerializeOptions, previous: Option<char>) -> char {
1084    let marker = if list.ordered {
1085        options
1086            .ordered_delimiter
1087            .map_or_else(|| written_marker(list), ordered_delimiter_char)
1088    } else {
1089        options
1090            .bullet
1091            .map_or_else(|| written_marker(list), bullet_char)
1092    };
1093    if previous == Some(marker) {
1094        let order: &[char] = if list.ordered {
1095            &['.', ')']
1096        } else {
1097            &['-', '*', '+']
1098        };
1099        let at = order.iter().position(|char| *char == marker).unwrap_or(0);
1100        return order[(at + 1) % order.len()];
1101    }
1102    marker
1103}
1104
1105/// The marker char the AST records for a list: its bullet, or its ordered
1106/// delimiter. A delimiter of the other list kind is written `-` or `.`.
1107/// Serialization and validation's adjacent-list rule both read it.
1108pub(crate) fn written_marker(list: &List) -> char {
1109    match (list.ordered, list.delimiter) {
1110        (false, ListDelimiter::Asterisk) => '*',
1111        (false, ListDelimiter::Plus) => '+',
1112        (false, _) => '-',
1113        (true, ListDelimiter::Paren) => ')',
1114        (true, _) => '.',
1115    }
1116}
1117
1118fn bullet_char(bullet: BulletMarker) -> char {
1119    match bullet {
1120        BulletMarker::Dash => '-',
1121        BulletMarker::Asterisk => '*',
1122        BulletMarker::Plus => '+',
1123    }
1124}
1125
1126fn ordered_delimiter_char(delimiter: OrderedDelimiter) -> char {
1127    match delimiter {
1128        OrderedDelimiter::Period => '.',
1129        OrderedDelimiter::Paren => ')',
1130    }
1131}
1132
1133fn write_list(
1134    node: &List,
1135    options: &SerializeOptions,
1136    marker_char: char,
1137) -> Result<String, SerializeError> {
1138    let mut output = String::new();
1139    // The number the previous item was written with.
1140    let mut previous: Option<u64> = None;
1141    for (index, item) in node.children.iter().enumerate() {
1142        if index > 0 {
1143            output.push_str(if node.tight { "\n" } else { "\n\n" });
1144        }
1145        output.push_str(&write_item(
1146            node,
1147            item,
1148            options,
1149            marker_char,
1150            &mut previous,
1151        )?);
1152    }
1153    Ok(output)
1154}
1155
1156/// The number an ordered item is written with: its own, or else one more than
1157/// the previous item's, `previous`, which it then becomes.
1158fn item_number(item: &ListItem, previous: &mut Option<u64>) -> u64 {
1159    // An item without a number counts on from the previous one, and stops at
1160    // the largest number a marker can hold.
1161    let number = item.number.unwrap_or_else(|| {
1162        previous.map_or(1, |previous| {
1163            previous
1164                .saturating_add(1)
1165                .min(crate::parse::MAX_ORDERED_NUMBER)
1166        })
1167    });
1168    *previous = Some(number);
1169    number
1170}
1171
1172/// Whether `block`, as the serializer writes it with default options, opens
1173/// with a backtick or a tilde after fewer than four spaces: a line the
1174/// parser does not take as a quote's paragraph's lazy line.
1175pub(crate) fn written_opens_fence_like(block: &Block) -> bool {
1176    write_block(block, &SerializeOptions::default()).is_ok_and(|written| {
1177        let content = written.trim_start_matches(' ');
1178        written.len() - content.len() < 4 && content.starts_with(['`', '~'])
1179    })
1180}
1181
1182/// One item of `list`, written with `marker_char`; `previous` is the number
1183/// the item before it was written with.
1184fn write_item(
1185    list: &List,
1186    item: &ListItem,
1187    options: &SerializeOptions,
1188    marker_char: char,
1189    previous: &mut Option<u64>,
1190) -> Result<String, SerializeError> {
1191    let marker = if list.ordered {
1192        format!("{}{marker_char} ", item_number(item, previous))
1193    } else {
1194        format!("{marker_char} ")
1195    };
1196    let inner = write_item_blocks(&item.children, options, list.tight, item.checked)?;
1197    Ok(
1198        if starts_below_bullet(list, item, marker_char, first_line(&inner)) {
1199            format!(
1200                "{}\n{}",
1201                marker.trim_end(),
1202                prefix_container_lines(&inner, &" ".repeat(marker.len()))
1203            )
1204        } else {
1205            format!("{marker}{}", indent_after_first_line(&inner, marker.len()))
1206        },
1207    )
1208}
1209
1210/// Whether `item` of `list`, written with `marker_char`, starts its content
1211/// on the line after its marker, `first_line` being the first line its
1212/// blocks are written as: where that line would read as padding after the
1213/// marker, or, after a bullet, as one thematic break with it (`- --`, or
1214/// the `- - -` of three nested empty items).
1215fn starts_below_bullet(list: &List, item: &ListItem, marker_char: char, first_line: &str) -> bool {
1216    opens_with_padding(first_line)
1217        || (!list.ordered
1218            && item.checked.is_none()
1219            && reads_as_thematic_break(marker_char, first_line))
1220}
1221
1222/// Whether `marker_char`, a space, and `rest` read as a thematic break of
1223/// that char: three or more `-` or `*`, with only spaces and tabs between.
1224pub(super) fn reads_as_thematic_break(marker_char: char, rest: &str) -> bool {
1225    matches!(marker_char, '-' | '*')
1226        && rest
1227            .chars()
1228            .all(|char| matches!(char, ' ' | '\t') || char == marker_char)
1229        && rest.chars().filter(|char| *char == marker_char).count() >= 2
1230}
1231
1232/// The first line the serializer writes `item` of `list` with, its marker
1233/// `marker_char` and `options`' text included.
1234fn item_first_line(
1235    list: &List,
1236    item: &ListItem,
1237    marker_char: char,
1238    options: &SerializeOptions,
1239) -> String {
1240    let marker = if list.ordered {
1241        format!("{}{marker_char}", item.number.unwrap_or(1))
1242    } else {
1243        String::from(marker_char)
1244    };
1245    let content = item_content_first_line(item, options);
1246    if starts_below_bullet(list, item, marker_char, &content) {
1247        marker
1248    } else {
1249        format!("{marker} {content}")
1250    }
1251}
1252
1253/// The first line of `item`'s blocks as the serializer writes them with
1254/// `options`: a nested list's first item is followed into, and a leaf block
1255/// is written to tell.
1256fn item_content_first_line(item: &ListItem, options: &SerializeOptions) -> String {
1257    if item.checked.is_some() {
1258        return String::from("[");
1259    }
1260    match item.children.first() {
1261        None => String::new(),
1262        Some(Block::List(nested)) => match nested.children.first() {
1263            Some(first) => {
1264                item_first_line(nested, first, list_marker(nested, options, None), options)
1265            }
1266            None => String::new(),
1267        },
1268        Some(
1269            block @ (Block::Paragraph(_)
1270            | Block::Heading(_)
1271            | Block::HtmlBlock(_)
1272            | Block::CodeBlock(_)
1273            | Block::ThematicBreak(_)),
1274        ) => write_block(block, options)
1275            .map(|written| String::from(first_line(&written)))
1276            .unwrap_or_default(),
1277        // Every other block opens with syntax of its own.
1278        Some(_) => String::from(">"),
1279    }
1280}
1281
1282/// `text` up to its first line ending.
1283fn first_line(text: &str) -> &str {
1284    text.split(['\n', '\r']).next().unwrap_or_default()
1285}
1286
1287/// Spaces or a tab opening an item's written content would read as padding
1288/// after the marker, so that content starts on the next line. Four spaces or
1289/// more open indented code, which the marker line holds after one space: an
1290/// item that starts empty cannot interrupt a paragraph.
1291fn opens_with_padding(inner: &str) -> bool {
1292    !inner.starts_with("    ") && inner.starts_with([' ', '\t'])
1293}
1294
1295/// Whether `block`, written with default options on the line after a
1296/// paragraph's, starts there instead of continuing the paragraph: the
1297/// parser's rule (`interrupts_paragraph`) for the block as the serializer
1298/// writes it, a setext heading and a list whose first item starts below its
1299/// bullet continuing the paragraph.
1300pub(crate) fn written_after_paragraph_interrupts(block: &Block) -> bool {
1301    match block {
1302        Block::Heading(heading) => !writes_setext(heading),
1303        Block::List(list) => {
1304            crate::parse::interrupts_paragraph(block) && !first_item_starts_below_its_bullet(list)
1305        }
1306        _ => crate::parse::interrupts_paragraph(block),
1307    }
1308}
1309
1310/// Whether, with default options, the serializer starts `list`'s first item
1311/// on a line after its bullet, so that the list, written after a paragraph's
1312/// line, cannot interrupt the paragraph. Only the item's first line is
1313/// written to tell.
1314pub(crate) fn first_item_starts_below_its_bullet(list: &List) -> bool {
1315    let Some(item) = list.children.first() else {
1316        return false;
1317    };
1318    let options = SerializeOptions::default();
1319    let content = item_content_first_line(item, &options);
1320    starts_below_bullet(list, item, list_marker(list, &options, None), &content)
1321}
1322
1323/// An item's blocks; a task item's, `task` holding whether it is checked,
1324/// open with its checkbox: at the start of the first paragraph after the
1325/// definitions the item starts with, or else of the item.
1326fn write_item_blocks(
1327    blocks: &[Block],
1328    options: &SerializeOptions,
1329    tight: bool,
1330    task: Option<bool>,
1331) -> Result<String, SerializeError> {
1332    let join = if tight { Join::Line } else { Join::Gap };
1333    let Some(checked) = task else {
1334        return write_blocks(blocks, options, join, false, None);
1335    };
1336    let checkbox = if checked { "[x]" } else { "[ ]" };
1337    let definitions = blocks
1338        .iter()
1339        .take_while(|block| matches!(block, Block::Definition(_)))
1340        .count();
1341    if matches!(blocks.get(definitions), Some(Block::Paragraph(_))) {
1342        let before = write_blocks(&blocks[..definitions], options, join, false, None)?;
1343        let rest = write_blocks(&blocks[definitions..], options, join, false, None)?;
1344        let mut output = before;
1345        if !output.is_empty() {
1346            match join {
1347                Join::Gap => push_block_gap(&mut output),
1348                Join::Line => output.push('\n'),
1349            }
1350        }
1351        output.push_str(checkbox);
1352        output.push(' ');
1353        output.push_str(&rest);
1354        return Ok(output);
1355    }
1356    let written = write_blocks(blocks, options, join, false, None)?;
1357    Ok(if written.is_empty() {
1358        checkbox.into()
1359    } else {
1360        format!("{checkbox} {written}")
1361    })
1362}
1363
1364fn write_code_block(node: &CodeBlock, options: &SerializeOptions) -> String {
1365    match node.kind {
1366        CodeBlockKind::Indented => {
1367            // Each value line ends with a line ending; the block gap or the
1368            // final newline writes a last `\n`, so only `\r` and `\r\n` stay.
1369            let body = trim_trailing_newline(&node.value);
1370            let mut output = prefix_lines(body, "    ");
1371            let ending = &node.value[body.len()..];
1372            if matches!(ending, "\r" | "\r\n") {
1373                output.push_str(ending);
1374            }
1375            output
1376        }
1377        CodeBlockKind::Fenced { marker, length } => {
1378            let marker = code_block_fence_marker(node, marker, options);
1379            let (fence, indent) = code_block_fence(&node.value, marker, length.max(3));
1380            let mut opener = fence.clone();
1381            if let Some(info) = &node.info {
1382                opener.push(' ');
1383                opener.push_str(&escape_code_info(info));
1384            }
1385            let body = fenced_body(&opener, &node.value, &fence);
1386            if indent == 0 {
1387                body
1388            } else {
1389                prefix_lines(&body, &" ".repeat(indent))
1390            }
1391        }
1392    }
1393}
1394
1395/// A fenced block: `opener`, then `value` (whose lines each keep their line
1396/// ending, the last one optionally), then `closer`. An empty value writes no
1397/// line between the fences.
1398fn fenced_body(opener: &str, value: &str, closer: &str) -> String {
1399    let mut output = String::with_capacity(opener.len() + value.len() + closer.len() + 2);
1400    output.push_str(opener);
1401    output.push('\n');
1402    output.push_str(value);
1403    if !value.is_empty() && !ends_with_line_ending(value) {
1404        output.push('\n');
1405    }
1406    output.push_str(closer);
1407    output
1408}
1409
1410/// The fence char a code block is written with: tildes when its info string
1411/// holds a backtick, which a backtick fence's info string cannot, or else the
1412/// char the AST records or the options replace it with.
1413fn code_block_fence_marker(
1414    node: &CodeBlock,
1415    marker: FenceMarker,
1416    options: &SerializeOptions,
1417) -> FenceMarker {
1418    if node.info.as_deref().is_some_and(|info| info.contains('`')) {
1419        return FenceMarker::Tilde;
1420    }
1421    options.fence_marker.unwrap_or(marker)
1422}
1423
1424fn escape_code_info(input: &str) -> String {
1425    let mut output = String::new();
1426    // The parser trims the info string, so whitespace at either end is
1427    // written as a character reference.
1428    let inner_start = input.len() - input.trim_start_matches([' ', '\t']).len();
1429    let inner_end = input.trim_end_matches([' ', '\t']).len().max(inner_start);
1430    for (offset, char) in input.char_indices() {
1431        match char {
1432            ' ' | '\t' if offset < inner_start || offset >= inner_end => {
1433                output.push_str(&char_reference(char));
1434            }
1435            '\t' => output.push(char),
1436            char if char.is_control() => output.push_str(&char_reference(char)),
1437            '\\' | '&' => {
1438                output.push('\\');
1439                output.push(char);
1440            }
1441            _ => output.push(char),
1442        }
1443    }
1444    output
1445}
1446
1447/// A hexadecimal character reference for `char`.
1448fn char_reference(char: char) -> String {
1449    format!("&#x{:X};", char as u32)
1450}
1451
1452fn write_table(node: &Table) -> Result<String, SerializeError> {
1453    let mut output = String::new();
1454    for (row_index, row) in node.rows.iter().enumerate() {
1455        if row_index > 0 {
1456            output.push('\n');
1457        }
1458        output.push_str(&write_row(row)?);
1459        if row_index == 0 {
1460            output.push('\n');
1461            output.push_str(&write_delimiter_row(&node.alignments));
1462        }
1463    }
1464    Ok(output)
1465}
1466
1467fn write_delimiter_row(alignments: &[TableAlignment]) -> String {
1468    let cells = alignments
1469        .iter()
1470        .map(|alignment| match alignment {
1471            TableAlignment::None => "---",
1472            TableAlignment::Left => ":---",
1473            TableAlignment::Center => ":---:",
1474            TableAlignment::Right => "---:",
1475        })
1476        .collect::<Vec<_>>();
1477    format!("| {} |", cells.join(" | "))
1478}
1479
1480fn write_row(row: &TableRow) -> Result<String, SerializeError> {
1481    let cells = row
1482        .cells
1483        .iter()
1484        .map(|cell| {
1485            Ok(encode_cell(&match one_line(cell.source.as_deref(), true) {
1486                Some(text) => text.into(),
1487                None => write_inlines(&cell.children, Context::BLOCK)?,
1488            }))
1489        })
1490        .collect::<Result<Vec<_>, SerializeError>>()?;
1491    Ok(format!("| {} |", cells.join(" | ")))
1492}
1493
1494/// A cell's inline content, written as anywhere else, encoded as cell
1495/// source. The parser splits a row at each `|` after no backslash or an even
1496/// run of them, and reads an odd run before a `|` with one backslash less, so
1497/// a `\` added before each `|` of the first kind reads back as written. A `|`
1498/// that the content already puts after an odd run, an escaped `|` in text,
1499/// reads with one backslash less, still as an escaped `|`; validation rejects
1500/// the cell values whose meaning that would change.
1501fn encode_cell(written: &str) -> String {
1502    let mut output = String::with_capacity(written.len());
1503    let mut backslashes = 0usize;
1504    for char in written.chars() {
1505        if char == '|' && backslashes % 2 == 0 {
1506            output.push('\\');
1507        }
1508        backslashes = if char == '\\' { backslashes + 1 } else { 0 };
1509        output.push(char);
1510    }
1511    output
1512}
1513
1514/// Where inline content is written, as the value encodings of its nodes read
1515/// it.
1516#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1517struct Context {
1518    /// In a heading, which a line ending would end.
1519    heading: bool,
1520}
1521
1522impl Context {
1523    const BLOCK: Self = Self { heading: false };
1524    const HEADING: Self = Self { heading: true };
1525}
1526
1527fn write_inlines(inlines: &[Inline], context: Context) -> Result<String, SerializeError> {
1528    let mut output = InlineOut::default();
1529    for inline in inlines {
1530        write_inline(inline, context, &mut output)?;
1531    }
1532    Ok(output.text)
1533}
1534
1535/// The Markdown written so far for the inline content of one block, cell, or
1536/// label, which the parser reads in one inline pass, and the backtick runs in
1537/// it that a code span's fence must not close.
1538#[derive(Default)]
1539struct InlineOut {
1540    text: String,
1541    /// `open_runs[n]`: a backtick run of length `n` that opens no code span
1542    /// was written, outside every code span: a later fence of that length
1543    /// would close it.
1544    open_runs: Vec<bool>,
1545    /// How far `text` has been read for `open_runs`.
1546    scanned: usize,
1547}
1548
1549impl InlineOut {
1550    /// Reads the text written since the last code span for backtick runs. A
1551    /// backslash escapes the first backtick of a run, which then opens with
1552    /// the rest of the run.
1553    fn scan_open_runs(&mut self) {
1554        let mut escaped = false;
1555        let mut run = 0;
1556        let mut run_escaped = false;
1557        for byte in self.text.as_bytes()[self.scanned..]
1558            .iter()
1559            .copied()
1560            .chain(core::iter::once(b' '))
1561        {
1562            if byte == b'`' {
1563                if run == 0 {
1564                    run_escaped = escaped;
1565                }
1566                run += 1;
1567                escaped = false;
1568                continue;
1569            }
1570            let opening = run - usize::from(run_escaped && run > 0);
1571            if opening > 0 {
1572                if self.open_runs.len() <= opening {
1573                    self.open_runs.resize(opening + 1, false);
1574                }
1575                self.open_runs[opening] = true;
1576            }
1577            run = 0;
1578            escaped = byte == b'\\' && !escaped;
1579        }
1580        self.scanned = self.text.len();
1581    }
1582
1583    fn is_open_run(&self, length: usize) -> bool {
1584        self.open_runs.get(length).copied().unwrap_or(false)
1585    }
1586
1587    /// Writes, with `write`, content that the parser reads whole from its
1588    /// first char, so no backtick run in it opens a code span.
1589    fn push_opaque(&mut self, write: impl FnOnce(&mut String)) {
1590        self.scan_open_runs();
1591        write(&mut self.text);
1592        self.scanned = self.text.len();
1593    }
1594}
1595
1596impl core::ops::Deref for InlineOut {
1597    type Target = String;
1598
1599    fn deref(&self) -> &String {
1600        &self.text
1601    }
1602}
1603
1604impl core::ops::DerefMut for InlineOut {
1605    fn deref_mut(&mut self) -> &mut String {
1606        &mut self.text
1607    }
1608}
1609
1610fn write_inline(
1611    inline: &Inline,
1612    context: Context,
1613    out: &mut InlineOut,
1614) -> Result<(), SerializeError> {
1615    match inline {
1616        Inline::Text(node) => out.push_str(&node.value),
1617        Inline::Escape(node) => {
1618            out.push('\\');
1619            out.push(node.value);
1620        }
1621        Inline::CharacterReference(node) => out.push_str(&node.reference),
1622        Inline::Emphasis(node) => {
1623            let delimiter = emphasis_delimiter(node.delimiter);
1624            write_span(out, delimiter, &node.children, delimiter, context)?;
1625        }
1626        Inline::Strong(node) => {
1627            let delimiter = emphasis_delimiter(node.delimiter).repeat(2);
1628            write_span(out, &delimiter, &node.children, &delimiter, context)?;
1629        }
1630        Inline::Delete(node) => write_span(out, "~~", &node.children, "~~", context)?,
1631        Inline::Mark(node) => write_span(out, "==", &node.children, "==", context)?,
1632        Inline::InlineFootnote(node) => write_span(out, "^[", &node.children, "]", context)?,
1633        Inline::Shortcode(node) => {
1634            out.push(':');
1635            out.push_str(&node.name);
1636            out.push(':');
1637        }
1638        Inline::Tag(node) => {
1639            out.push('#');
1640            out.push_str(&node.name);
1641        }
1642        Inline::Code(node) => write_code_span(node, out),
1643        Inline::Link(node) => {
1644            write_span(out, "[", &node.children, "](", context)?;
1645            out.push_opaque(|out| {
1646                write_resource(
1647                    out,
1648                    &node.destination,
1649                    node.destination_kind,
1650                    node.title.as_ref(),
1651                );
1652            });
1653        }
1654        Inline::Autolink(node) => out.push_opaque(|out| match node.form {
1655            AutolinkForm::Literal => out.push_str(&node.text),
1656            AutolinkForm::Angle => {
1657                out.push('<');
1658                out.push_str(&node.text);
1659                out.push('>');
1660            }
1661        }),
1662        Inline::Image(node) => {
1663            write_span(out, "![", &node.alt, "](", context)?;
1664            out.push_opaque(|out| {
1665                write_resource(
1666                    out,
1667                    &node.destination,
1668                    node.destination_kind,
1669                    node.title.as_ref(),
1670                );
1671            });
1672        }
1673        Inline::LinkReference(node) => {
1674            write_reference(out, "[", &node.children, node.kind, &node.label, context)?;
1675        }
1676        Inline::ImageReference(node) => {
1677            write_reference(out, "![", &node.alt, node.kind, &node.label, context)?;
1678        }
1679        Inline::Html(node) => out.push_opaque(|out| out.push_str(&node.value)),
1680        Inline::SoftBreak(_) if context.heading => out.push(' '),
1681        Inline::SoftBreak(_) => out.push('\n'),
1682        Inline::LineBreak(node) => match node.kind {
1683            LineBreakKind::Backslash => out.push_str("\\\n"),
1684            LineBreakKind::Spaces => out.push_str("  \n"),
1685        },
1686        Inline::Math(node) => {
1687            let math = write_inline_math(node);
1688            out.push_opaque(|out| out.push_str(&math));
1689        }
1690        Inline::FootnoteReference(node) => {
1691            out.push_str("[^");
1692            out.push_str(&node.label);
1693            out.push(']');
1694        }
1695        Inline::WikiLink(node) => out.push_opaque(|out| {
1696            if node.embed {
1697                out.push('!');
1698            }
1699            out.push_str("[[");
1700            out.push_str(&node.target);
1701            if node.target != node.label {
1702                out.push('|');
1703                out.push_str(&node.label);
1704            }
1705            out.push_str("]]");
1706        }),
1707        Inline::TextDirective(node) => {
1708            // The label is read in an inline pass of its own.
1709            let label = if node.label.is_empty() {
1710                String::new()
1711            } else {
1712                format!("[{}]", write_inlines(&node.label, context)?)
1713            };
1714            out.push_opaque(|out| {
1715                out.push(':');
1716                out.push_str(&node.name);
1717                out.push_str(&label);
1718                out.push_str(&write_attributes(&node.attributes));
1719            });
1720        }
1721    }
1722    Ok(())
1723}
1724
1725fn emphasis_delimiter(delimiter: EmphasisDelimiter) -> &'static str {
1726    match delimiter {
1727        EmphasisDelimiter::Asterisk => "*",
1728        EmphasisDelimiter::Underscore => "_",
1729    }
1730}
1731
1732/// Writes `open`, `children`, and `close`.
1733fn write_span(
1734    out: &mut InlineOut,
1735    open: &str,
1736    children: &[Inline],
1737    close: &str,
1738    context: Context,
1739) -> Result<(), SerializeError> {
1740    out.push_str(open);
1741    for child in children {
1742        write_inline(child, context, out)?;
1743    }
1744    out.push_str(close);
1745    Ok(())
1746}
1747
1748/// A link's or image's `destination "title")`, after its `](`.
1749fn write_resource(
1750    out: &mut String,
1751    destination: &str,
1752    kind: LinkDestinationKind,
1753    title: Option<&Title>,
1754) {
1755    out.push_str(&write_destination(destination, kind));
1756    if let Some(title) = title {
1757        out.push(' ');
1758        out.push_str(&write_title(title));
1759    }
1760    out.push(')');
1761}
1762
1763/// What follows a reference's text: nothing, `[]`, or `[label]`.
1764/// A reference link or image. A full reference writes its text and then its
1765/// label; a shortcut or collapsed one writes its label as written, which is
1766/// both its text and its key, so the text it reads back as matches the
1767/// definition it names.
1768fn write_reference(
1769    out: &mut InlineOut,
1770    open: &str,
1771    children: &[Inline],
1772    kind: ReferenceKind,
1773    label: &str,
1774    context: Context,
1775) -> Result<(), SerializeError> {
1776    match kind {
1777        ReferenceKind::Full => write_span(out, open, children, "]", context)?,
1778        ReferenceKind::Shortcut | ReferenceKind::Collapsed => {
1779            out.push_str(open);
1780            out.push_str(label);
1781            out.push(']');
1782        }
1783    }
1784    out.push_opaque(|out| write_reference_kind(out, kind, label));
1785    Ok(())
1786}
1787
1788fn write_reference_kind(out: &mut String, kind: ReferenceKind, label: &str) {
1789    match kind {
1790        ReferenceKind::Shortcut => {}
1791        ReferenceKind::Collapsed => out.push_str("[]"),
1792        ReferenceKind::Full => {
1793            out.push('[');
1794            // A label is matched as written.
1795            out.push_str(label);
1796            out.push(']');
1797        }
1798    }
1799}
1800
1801/// Writes a code span from its value: fenced by the shortest backtick run
1802/// that neither the value holds nor a run written before it in the same
1803/// inline content opens, and padded with a space at each end when the value
1804/// would otherwise lose an end to the fence or to the space stripping.
1805fn write_code_span(node: &CodeInline, out: &mut InlineOut) {
1806    let value = &node.value;
1807    out.scan_open_runs();
1808    let held = backtick_runs(value);
1809    // An escaped backtick written just before joins the opening fence into
1810    // one longer run, which an earlier open run of that length would take as
1811    // its close.
1812    let before = out.len() - out.trim_end_matches('`').len();
1813    let length = (1..)
1814        .find(|length| {
1815            !held.get(*length).copied().unwrap_or(false)
1816                && !out.is_open_run(*length)
1817                && (before == 0 || !out.is_open_run(before + *length))
1818        })
1819        .unwrap_or(1);
1820    let fence = "`".repeat(length);
1821    out.push_str(&fence);
1822    if code_span_needs_padding(value) {
1823        out.push(' ');
1824        out.push_str(value);
1825        out.push(' ');
1826    } else {
1827        out.push_str(value);
1828    }
1829    out.push_str(&fence);
1830    // The span's own runs are matched; what follows is read afresh.
1831    out.scanned = out.text.len();
1832}
1833
1834/// `runs[n]`: whether `value` holds a maximal run of `n` backticks.
1835fn backtick_runs(value: &str) -> Vec<bool> {
1836    // A value of `len` bytes holds runs of at most `len` backticks.
1837    let mut runs = alloc::vec![false; value.len() + 1];
1838    let mut run = 0;
1839    for byte in value.bytes() {
1840        if byte == b'`' {
1841            run += 1;
1842        } else {
1843            runs[run] = true;
1844            run = 0;
1845        }
1846    }
1847    runs[run] = true;
1848    runs
1849}
1850
1851fn code_span_needs_padding(input: &str) -> bool {
1852    input.starts_with('`')
1853        || input.ends_with('`')
1854        || (input.starts_with(' ') && input.ends_with(' ') && input.chars().any(|char| char != ' '))
1855}
1856
1857pub(crate) fn write_inline_math(node: &MathInline) -> String {
1858    match node.kind {
1859        MathInlineKind::Code => format!("$`{}`$", node.value),
1860        // Dollar math is written verbatim behind its exact-length fence.
1861        MathInlineKind::Dollar { dollars } => {
1862            let fence = "$".repeat(usize::from(dollars));
1863            format!("{fence}{}{fence}", node.value)
1864        }
1865    }
1866}
1867
1868/// A directive's `[label]`, written as the text it keeps if any, or nothing
1869/// for an empty label.
1870fn write_directive_label(label: &[Inline], source: Option<&str>) -> Result<String, SerializeError> {
1871    if let Some(text) = one_line(source, true) {
1872        return Ok(if text.is_empty() {
1873            String::new()
1874        } else {
1875            format!("[{text}]")
1876        });
1877    }
1878    if label.is_empty() {
1879        Ok(String::new())
1880    } else {
1881        Ok(format!("[{}]", write_inlines(label, Context::BLOCK)?))
1882    }
1883}
1884
1885/// The text a leaf keeps, where it can be written in place of its inlines:
1886/// on one line when `single` holds, as in a cell, a label, or an ATX heading,
1887/// which a line ending would end.
1888fn one_line(source: Option<&str>, single: bool) -> Option<&str> {
1889    source.filter(|text| !single || !text.contains(['\n', '\r']))
1890}
1891
1892fn write_attributes(attributes: &[DirectiveAttribute]) -> String {
1893    if attributes.is_empty() {
1894        return String::new();
1895    }
1896    let mut output = String::from("{");
1897    for (index, attribute) in attributes.iter().enumerate() {
1898        if index > 0 {
1899            output.push(' ');
1900        }
1901        match (&*attribute.name, &attribute.value) {
1902            ("id", Some(value)) if is_directive_shorthand_value(value) => {
1903                output.push('#');
1904                output.push_str(value);
1905            }
1906            ("class", Some(value)) if is_directive_shorthand_value(value) => {
1907                output.push('.');
1908                output.push_str(value);
1909            }
1910            (_, Some(value)) => {
1911                output.push_str(&attribute.name);
1912                output.push_str("=\"");
1913                output.push_str(&escape_title(value, LinkTitleKind::DoubleQuote));
1914                output.push('"');
1915            }
1916            (_, None) => output.push_str(&attribute.name),
1917        }
1918    }
1919    output.push('}');
1920    output
1921}
1922
1923fn is_directive_shorthand_value(input: &str) -> bool {
1924    !input.is_empty()
1925        && input
1926            .chars()
1927            .all(|char| char.is_ascii_alphanumeric() || matches!(char, '_' | '-'))
1928}
1929
1930fn write_destination(input: &str, kind: LinkDestinationKind) -> String {
1931    match kind {
1932        LinkDestinationKind::Omitted if input.is_empty() => String::new(),
1933        LinkDestinationKind::Angle => {
1934            let mut output = String::from("<");
1935            for char in input.chars() {
1936                match char {
1937                    char if char.is_control() => output.push_str(&char_reference(char)),
1938                    '\\' | '<' | '>' => {
1939                        output.push('\\');
1940                        output.push(char);
1941                    }
1942                    _ => output.push(char),
1943                }
1944            }
1945            output.push('>');
1946            output
1947        }
1948        LinkDestinationKind::Bare | LinkDestinationKind::Omitted if input.is_empty() => "<>".into(),
1949        LinkDestinationKind::Bare | LinkDestinationKind::Omitted => {
1950            let mut output = String::new();
1951            for char in input.chars() {
1952                match char {
1953                    // A space would end the destination, and `\ ` is no escape.
1954                    char if char.is_control() || char == ' ' => {
1955                        output.push_str(&char_reference(char));
1956                    }
1957                    '(' | ')' | '\\' | '<' | '>' | '&' => {
1958                        output.push('\\');
1959                        output.push(char);
1960                    }
1961                    _ => output.push(char),
1962                }
1963            }
1964            output
1965        }
1966    }
1967}
1968
1969fn write_title(title: &Title) -> String {
1970    let (open, close) = match title.kind {
1971        LinkTitleKind::DoubleQuote => ('"', '"'),
1972        LinkTitleKind::SingleQuote => ('\'', '\''),
1973        LinkTitleKind::Paren => ('(', ')'),
1974    };
1975    let mut output = String::new();
1976    output.push(open);
1977    output.push_str(&escape_title(&title.value, title.kind));
1978    output.push(close);
1979    output
1980}
1981
1982fn escape_title(input: &str, kind: LinkTitleKind) -> String {
1983    let mut output = String::new();
1984    for char in input.chars() {
1985        match char {
1986            char if char.is_control() => output.push_str(&char_reference(char)),
1987            '\\' | '&' => {
1988                output.push('\\');
1989                output.push(char);
1990            }
1991            '"' if kind == LinkTitleKind::DoubleQuote => output.push_str("\\\""),
1992            '\'' if kind == LinkTitleKind::SingleQuote => output.push_str("\\'"),
1993            '(' | ')' if kind == LinkTitleKind::Paren => {
1994                output.push('\\');
1995                output.push(char);
1996            }
1997            _ => output.push(char),
1998        }
1999    }
2000    output
2001}
2002
2003/// Prefixes every line of `input` with a container's `prefix`, keeping each
2004/// line's ending; an empty line takes the prefix without its trailing
2005/// whitespace, so that no blank line the writer writes ends in whitespace.
2006fn prefix_container_lines(input: &str, prefix: &str) -> String {
2007    prefix_lines_with(input, prefix, prefix.trim_end_matches([' ', '\t']))
2008}
2009
2010/// Prefixes every line of `input` with `prefix`, keeping each line's ending.
2011fn prefix_lines(input: &str, prefix: &str) -> String {
2012    prefix_lines_with(input, prefix, prefix)
2013}
2014
2015/// Prefixes every line of `input` with `prefix`, or an empty one with
2016/// `empty_prefix`, keeping each line's ending.
2017fn prefix_lines_with(input: &str, prefix: &str, empty_prefix: &str) -> String {
2018    if input.is_empty() {
2019        return String::new();
2020    }
2021    let bytes = input.as_bytes();
2022    let mut output = String::new();
2023    let mut line_start = 0;
2024    let mut cursor = 0;
2025    while cursor < input.len() {
2026        let eol_end = match bytes[cursor] {
2027            b'\n' => Some(cursor + 1),
2028            b'\r' if bytes.get(cursor + 1) == Some(&b'\n') => Some(cursor + 2),
2029            b'\r' => Some(cursor + 1),
2030            _ => None,
2031        };
2032        if let Some(end) = eol_end {
2033            output.push_str(if cursor == line_start {
2034                empty_prefix
2035            } else {
2036                prefix
2037            });
2038            output.push_str(&input[line_start..end]);
2039            cursor = end;
2040            line_start = cursor;
2041        } else {
2042            cursor += 1;
2043        }
2044    }
2045    if line_start < input.len() {
2046        output.push_str(prefix);
2047        output.push_str(&input[line_start..]);
2048    }
2049    output
2050}
2051
2052fn indent_after_first_line(input: &str, width: usize) -> String {
2053    indent_lines_of(input, width)
2054}
2055
2056fn indent_continuation(input: &str) -> String {
2057    indent_lines_of(input, 4)
2058}
2059
2060/// Indents every line of `input` after its first by `width` spaces, as a
2061/// container's continuation; an empty line stays empty. Each line keeps its
2062/// line ending, a final one included.
2063fn indent_lines_of(input: &str, width: usize) -> String {
2064    let mut output = String::with_capacity(input.len());
2065    let bytes = input.as_bytes();
2066    let mut line_start = 0;
2067    let mut index = 0;
2068    let push = |output: &mut String, line: &str, number: usize| {
2069        if number > 0 && !line.starts_with(['\n', '\r']) {
2070            output.extend(core::iter::repeat_n(' ', width));
2071        }
2072        output.push_str(line);
2073    };
2074    let mut number = 0;
2075    while index < bytes.len() {
2076        let end = match bytes[index] {
2077            b'\n' => Some(index + 1),
2078            b'\r' if bytes.get(index + 1) == Some(&b'\n') => Some(index + 2),
2079            b'\r' => Some(index + 1),
2080            _ => None,
2081        };
2082        match end {
2083            Some(end) => {
2084                push(&mut output, &input[line_start..end], number);
2085                number += 1;
2086                index = end;
2087                line_start = end;
2088            }
2089            None => index += 1,
2090        }
2091    }
2092    if line_start < input.len() {
2093        push(&mut output, &input[line_start..], number);
2094    }
2095    output
2096}
2097
2098fn trim_trailing_newline(input: &str) -> &str {
2099    input.trim_end_matches('\n').trim_end_matches('\r')
2100}
2101
2102fn ends_with_line_ending(input: &str) -> bool {
2103    input.ends_with('\n') || input.ends_with('\r')
2104}
2105
2106/// The fence for a code block's `value` and the columns the block is indented
2107/// by, so that no line of `value` closes it. A closing fence is up to three
2108/// spaces, at least as many marker chars, and nothing else but spaces and
2109/// tabs. The fence keeps `min_len` chars when indenting the block (which the
2110/// value's lines lose again) moves every closing-like line past three spaces;
2111/// otherwise it grows past the longest of them.
2112fn code_block_fence(value: &str, marker: FenceMarker, min_len: usize) -> (String, usize) {
2113    let char = match marker {
2114        FenceMarker::Backtick => '`',
2115        FenceMarker::Tilde => '~',
2116    };
2117    let closing_like = |line: &str, length: usize| {
2118        let indent = line.len() - line.trim_start_matches(' ').len();
2119        let rest = &line[indent..];
2120        let run = rest.len() - rest.trim_start_matches(char).len();
2121        (indent <= 3 && run >= length && rest[run..].trim_matches([' ', '\t']).is_empty())
2122            .then_some((indent, run))
2123    };
2124    let lines = || value.split(['\n', '\r']);
2125    let least_indent = lines()
2126        .filter_map(|line| closing_like(line, min_len))
2127        .map(|(indent, _)| indent)
2128        .min();
2129    match least_indent {
2130        None => (char.to_string().repeat(min_len), 0),
2131        Some(indent) if indent > 0 => (char.to_string().repeat(min_len), 4 - indent),
2132        Some(_) => {
2133            let mut length = min_len;
2134            for line in lines() {
2135                if let Some((_, run)) = closing_like(line, length) {
2136                    length = run + 1;
2137                }
2138            }
2139            (char.to_string().repeat(length), 0)
2140        }
2141    }
2142}
2143
2144fn block_math_fence(input: &str) -> String {
2145    let mut length = 2;
2146    for line in trim_trailing_newline(input).lines() {
2147        let trimmed = line.trim();
2148        if trimmed.len() >= 2 && trimmed.chars().all(|char| char == '$') {
2149            length = length.max(trimmed.len() + 1);
2150        }
2151    }
2152    "$".repeat(length)
2153}
2154
2155fn directive_fence(inner: &str) -> String {
2156    let mut length = 3;
2157    for line in inner.lines() {
2158        if let Some(closing) = directive_closing_fence_len(line) {
2159            length = length.max(closing + 1);
2160        }
2161    }
2162    ":".repeat(length)
2163}
2164
2165fn directive_closing_fence_len(line: &str) -> Option<usize> {
2166    let trimmed = trim_up_to_three_indent_columns(line)?;
2167    let length = trimmed
2168        .as_bytes()
2169        .iter()
2170        .take_while(|byte| **byte == b':')
2171        .count();
2172    if length >= 3 && trimmed[length..].trim().is_empty() {
2173        Some(length)
2174    } else {
2175        None
2176    }
2177}
2178
2179fn trim_up_to_three_indent_columns(input: &str) -> Option<&str> {
2180    let mut columns = 0usize;
2181    let mut bytes = 0usize;
2182    for byte in input.as_bytes() {
2183        match *byte {
2184            b' ' => columns += 1,
2185            b'\t' => columns += 4 - (columns % 4),
2186            _ => break,
2187        }
2188        bytes += 1;
2189    }
2190    (columns <= 3).then_some(&input[bytes..])
2191}
2192
2193#[cfg(test)]
2194mod tests {
2195    use super::*;
2196
2197    // serialization: Unchanged source is kept
2198    #[test]
2199    fn the_read_back_check_names_each_block_that_reads_differently() {
2200        let blocks = crate::parse("> x\n\n# y\n\nz").document.children;
2201        assert_eq!(misread(&blocks, "> x\n\n# y\n\nz", &[0, 2]), [0usize; 0]);
2202        // A block read differently in its place.
2203        assert_eq!(misread(&blocks, "> w\n\n# y\n\nz", &[0, 2]), [0]);
2204        assert_eq!(misread(&blocks, "> w\n\n# y\n\nz", &[2]), [0usize; 0]);
2205        // `z` read into the quote: the block count differs.
2206        assert_eq!(misread(&blocks, "> x\nz\n\n# y", &[0, 2]), [0, 2]);
2207    }
2208}