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    let break_after_bullet = breaks_after_bullet(list, item, marker_char);
1198    let opens_with_whitespace = opens_with_padding(&inner);
1199    Ok(if break_after_bullet || opens_with_whitespace {
1200        format!(
1201            "{}\n{}",
1202            marker.trim_end(),
1203            prefix_container_lines(&inner, &" ".repeat(marker.len()))
1204        )
1205    } else {
1206        format!("{marker}{}", indent_after_first_line(&inner, marker.len()))
1207    })
1208}
1209
1210fn thematic_break_char(marker: ThematicBreakMarker) -> char {
1211    match marker {
1212        ThematicBreakMarker::Dash => '-',
1213        ThematicBreakMarker::Asterisk => '*',
1214        ThematicBreakMarker::Underscore => '_',
1215    }
1216}
1217
1218/// An item's blocks; a task item's, `task` holding whether it is checked,
1219/// open with its checkbox: at the start of the first paragraph after the
1220/// definitions the item starts with, or else of the item.
1221/// A thematic break of the bullet's own char right after the bullet would
1222/// read as one longer break, so it starts on the next line.
1223fn breaks_after_bullet(list: &List, item: &ListItem, marker_char: char) -> bool {
1224    !list.ordered
1225        && item.checked.is_none()
1226        && matches!(
1227            item.children.first(),
1228            Some(Block::ThematicBreak(ThematicBreak { marker, .. }))
1229                if thematic_break_char(*marker) == marker_char
1230        )
1231}
1232
1233/// Spaces or a tab opening an item's written content would read as padding
1234/// after the marker, so that content starts on the next line. Four spaces or
1235/// more open indented code, which the marker line holds after one space: an
1236/// item that starts empty cannot interrupt a paragraph.
1237fn opens_with_padding(inner: &str) -> bool {
1238    !inner.starts_with("    ") && inner.starts_with([' ', '\t'])
1239}
1240
1241/// Whether `block`, written with default options on the line after a
1242/// paragraph's, starts there instead of continuing the paragraph: the
1243/// parser's rule (`interrupts_paragraph`) for the block as the serializer
1244/// writes it, a setext heading and a list whose first item starts below its
1245/// bullet continuing the paragraph.
1246pub(crate) fn written_after_paragraph_interrupts(block: &Block) -> bool {
1247    match block {
1248        Block::Heading(heading) => !writes_setext(heading),
1249        Block::List(list) => {
1250            crate::parse::interrupts_paragraph(block) && !first_item_starts_below_its_bullet(list)
1251        }
1252        _ => crate::parse::interrupts_paragraph(block),
1253    }
1254}
1255
1256/// Whether, with default options, the serializer starts `list`'s first item
1257/// on a line after its bullet, so that the list, written after a paragraph's
1258/// line, cannot interrupt the paragraph. Only a leaf block can open with
1259/// whitespace, so only a leading leaf block is written to tell.
1260pub(crate) fn first_item_starts_below_its_bullet(list: &List) -> bool {
1261    let Some(item) = list.children.first() else {
1262        return false;
1263    };
1264    let options = SerializeOptions::default();
1265    if breaks_after_bullet(list, item, list_marker(list, &options, None)) {
1266        return true;
1267    }
1268    if item.checked.is_some() {
1269        return false;
1270    }
1271    match item.children.first() {
1272        Some(
1273            block @ (Block::Paragraph(_)
1274            | Block::Heading(_)
1275            | Block::HtmlBlock(_)
1276            | Block::CodeBlock(_)),
1277        ) => write_block(block, &options).is_ok_and(|written| opens_with_padding(&written)),
1278        _ => false,
1279    }
1280}
1281
1282fn write_item_blocks(
1283    blocks: &[Block],
1284    options: &SerializeOptions,
1285    tight: bool,
1286    task: Option<bool>,
1287) -> Result<String, SerializeError> {
1288    let join = if tight { Join::Line } else { Join::Gap };
1289    let Some(checked) = task else {
1290        return write_blocks(blocks, options, join, false, None);
1291    };
1292    let checkbox = if checked { "[x]" } else { "[ ]" };
1293    let definitions = blocks
1294        .iter()
1295        .take_while(|block| matches!(block, Block::Definition(_)))
1296        .count();
1297    if matches!(blocks.get(definitions), Some(Block::Paragraph(_))) {
1298        let before = write_blocks(&blocks[..definitions], options, join, false, None)?;
1299        let rest = write_blocks(&blocks[definitions..], options, join, false, None)?;
1300        let mut output = before;
1301        if !output.is_empty() {
1302            match join {
1303                Join::Gap => push_block_gap(&mut output),
1304                Join::Line => output.push('\n'),
1305            }
1306        }
1307        output.push_str(checkbox);
1308        output.push(' ');
1309        output.push_str(&rest);
1310        return Ok(output);
1311    }
1312    let written = write_blocks(blocks, options, join, false, None)?;
1313    Ok(if written.is_empty() {
1314        checkbox.into()
1315    } else {
1316        format!("{checkbox} {written}")
1317    })
1318}
1319
1320fn write_code_block(node: &CodeBlock, options: &SerializeOptions) -> String {
1321    match node.kind {
1322        CodeBlockKind::Indented => {
1323            // Each value line ends with a line ending; the block gap or the
1324            // final newline writes a last `\n`, so only `\r` and `\r\n` stay.
1325            let body = trim_trailing_newline(&node.value);
1326            let mut output = prefix_lines(body, "    ");
1327            let ending = &node.value[body.len()..];
1328            if matches!(ending, "\r" | "\r\n") {
1329                output.push_str(ending);
1330            }
1331            output
1332        }
1333        CodeBlockKind::Fenced { marker, length } => {
1334            let marker = code_block_fence_marker(node, marker, options);
1335            let (fence, indent) = code_block_fence(&node.value, marker, length.max(3));
1336            let mut opener = fence.clone();
1337            if let Some(info) = &node.info {
1338                opener.push(' ');
1339                opener.push_str(&escape_code_info(info));
1340            }
1341            let body = fenced_body(&opener, &node.value, &fence);
1342            if indent == 0 {
1343                body
1344            } else {
1345                prefix_lines(&body, &" ".repeat(indent))
1346            }
1347        }
1348    }
1349}
1350
1351/// A fenced block: `opener`, then `value` (whose lines each keep their line
1352/// ending, the last one optionally), then `closer`. An empty value writes no
1353/// line between the fences.
1354fn fenced_body(opener: &str, value: &str, closer: &str) -> String {
1355    let mut output = String::with_capacity(opener.len() + value.len() + closer.len() + 2);
1356    output.push_str(opener);
1357    output.push('\n');
1358    output.push_str(value);
1359    if !value.is_empty() && !ends_with_line_ending(value) {
1360        output.push('\n');
1361    }
1362    output.push_str(closer);
1363    output
1364}
1365
1366/// The fence char a code block is written with: tildes when its info string
1367/// holds a backtick, which a backtick fence's info string cannot, or else the
1368/// char the AST records or the options replace it with.
1369fn code_block_fence_marker(
1370    node: &CodeBlock,
1371    marker: FenceMarker,
1372    options: &SerializeOptions,
1373) -> FenceMarker {
1374    if node.info.as_deref().is_some_and(|info| info.contains('`')) {
1375        return FenceMarker::Tilde;
1376    }
1377    options.fence_marker.unwrap_or(marker)
1378}
1379
1380fn escape_code_info(input: &str) -> String {
1381    let mut output = String::new();
1382    // The parser trims the info string, so whitespace at either end is
1383    // written as a character reference.
1384    let inner_start = input.len() - input.trim_start_matches([' ', '\t']).len();
1385    let inner_end = input.trim_end_matches([' ', '\t']).len().max(inner_start);
1386    for (offset, char) in input.char_indices() {
1387        match char {
1388            ' ' | '\t' if offset < inner_start || offset >= inner_end => {
1389                output.push_str(&char_reference(char));
1390            }
1391            '\t' => output.push(char),
1392            char if char.is_control() => output.push_str(&char_reference(char)),
1393            '\\' | '&' => {
1394                output.push('\\');
1395                output.push(char);
1396            }
1397            _ => output.push(char),
1398        }
1399    }
1400    output
1401}
1402
1403/// A hexadecimal character reference for `char`.
1404fn char_reference(char: char) -> String {
1405    format!("&#x{:X};", char as u32)
1406}
1407
1408fn write_table(node: &Table) -> Result<String, SerializeError> {
1409    let mut output = String::new();
1410    for (row_index, row) in node.rows.iter().enumerate() {
1411        if row_index > 0 {
1412            output.push('\n');
1413        }
1414        output.push_str(&write_row(row)?);
1415        if row_index == 0 {
1416            output.push('\n');
1417            output.push_str(&write_delimiter_row(&node.alignments));
1418        }
1419    }
1420    Ok(output)
1421}
1422
1423fn write_delimiter_row(alignments: &[TableAlignment]) -> String {
1424    let cells = alignments
1425        .iter()
1426        .map(|alignment| match alignment {
1427            TableAlignment::None => "---",
1428            TableAlignment::Left => ":---",
1429            TableAlignment::Center => ":---:",
1430            TableAlignment::Right => "---:",
1431        })
1432        .collect::<Vec<_>>();
1433    format!("| {} |", cells.join(" | "))
1434}
1435
1436fn write_row(row: &TableRow) -> Result<String, SerializeError> {
1437    let cells = row
1438        .cells
1439        .iter()
1440        .map(|cell| {
1441            Ok(encode_cell(&match one_line(cell.source.as_deref(), true) {
1442                Some(text) => text.into(),
1443                None => write_inlines(&cell.children, Context::BLOCK)?,
1444            }))
1445        })
1446        .collect::<Result<Vec<_>, SerializeError>>()?;
1447    Ok(format!("| {} |", cells.join(" | ")))
1448}
1449
1450/// A cell's inline content, written as anywhere else, encoded as cell
1451/// source. The parser splits a row at each `|` after no backslash or an even
1452/// run of them, and reads an odd run before a `|` with one backslash less, so
1453/// a `\` added before each `|` of the first kind reads back as written. A `|`
1454/// that the content already puts after an odd run, an escaped `|` in text,
1455/// reads with one backslash less, still as an escaped `|`; validation rejects
1456/// the cell values whose meaning that would change.
1457fn encode_cell(written: &str) -> String {
1458    let mut output = String::with_capacity(written.len());
1459    let mut backslashes = 0usize;
1460    for char in written.chars() {
1461        if char == '|' && backslashes % 2 == 0 {
1462            output.push('\\');
1463        }
1464        backslashes = if char == '\\' { backslashes + 1 } else { 0 };
1465        output.push(char);
1466    }
1467    output
1468}
1469
1470/// Where inline content is written, as the value encodings of its nodes read
1471/// it.
1472#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1473struct Context {
1474    /// In a heading, which a line ending would end.
1475    heading: bool,
1476}
1477
1478impl Context {
1479    const BLOCK: Self = Self { heading: false };
1480    const HEADING: Self = Self { heading: true };
1481}
1482
1483fn write_inlines(inlines: &[Inline], context: Context) -> Result<String, SerializeError> {
1484    let mut output = InlineOut::default();
1485    for inline in inlines {
1486        write_inline(inline, context, &mut output)?;
1487    }
1488    Ok(output.text)
1489}
1490
1491/// The Markdown written so far for the inline content of one block, cell, or
1492/// label, which the parser reads in one inline pass, and the backtick runs in
1493/// it that a code span's fence must not close.
1494#[derive(Default)]
1495struct InlineOut {
1496    text: String,
1497    /// `open_runs[n]`: a backtick run of length `n` that opens no code span
1498    /// was written, outside every code span: a later fence of that length
1499    /// would close it.
1500    open_runs: Vec<bool>,
1501    /// How far `text` has been read for `open_runs`.
1502    scanned: usize,
1503}
1504
1505impl InlineOut {
1506    /// Reads the text written since the last code span for backtick runs. A
1507    /// backslash escapes the first backtick of a run, which then opens with
1508    /// the rest of the run.
1509    fn scan_open_runs(&mut self) {
1510        let mut escaped = false;
1511        let mut run = 0;
1512        let mut run_escaped = false;
1513        for byte in self.text.as_bytes()[self.scanned..]
1514            .iter()
1515            .copied()
1516            .chain(core::iter::once(b' '))
1517        {
1518            if byte == b'`' {
1519                if run == 0 {
1520                    run_escaped = escaped;
1521                }
1522                run += 1;
1523                escaped = false;
1524                continue;
1525            }
1526            let opening = run - usize::from(run_escaped && run > 0);
1527            if opening > 0 {
1528                if self.open_runs.len() <= opening {
1529                    self.open_runs.resize(opening + 1, false);
1530                }
1531                self.open_runs[opening] = true;
1532            }
1533            run = 0;
1534            escaped = byte == b'\\' && !escaped;
1535        }
1536        self.scanned = self.text.len();
1537    }
1538
1539    fn is_open_run(&self, length: usize) -> bool {
1540        self.open_runs.get(length).copied().unwrap_or(false)
1541    }
1542
1543    /// Writes, with `write`, content that the parser reads whole from its
1544    /// first char, so no backtick run in it opens a code span.
1545    fn push_opaque(&mut self, write: impl FnOnce(&mut String)) {
1546        self.scan_open_runs();
1547        write(&mut self.text);
1548        self.scanned = self.text.len();
1549    }
1550}
1551
1552impl core::ops::Deref for InlineOut {
1553    type Target = String;
1554
1555    fn deref(&self) -> &String {
1556        &self.text
1557    }
1558}
1559
1560impl core::ops::DerefMut for InlineOut {
1561    fn deref_mut(&mut self) -> &mut String {
1562        &mut self.text
1563    }
1564}
1565
1566fn write_inline(
1567    inline: &Inline,
1568    context: Context,
1569    out: &mut InlineOut,
1570) -> Result<(), SerializeError> {
1571    match inline {
1572        Inline::Text(node) => out.push_str(&node.value),
1573        Inline::Escape(node) => {
1574            out.push('\\');
1575            out.push(node.value);
1576        }
1577        Inline::CharacterReference(node) => out.push_str(&node.reference),
1578        Inline::Emphasis(node) => {
1579            let delimiter = emphasis_delimiter(node.delimiter);
1580            write_span(out, delimiter, &node.children, delimiter, context)?;
1581        }
1582        Inline::Strong(node) => {
1583            let delimiter = emphasis_delimiter(node.delimiter).repeat(2);
1584            write_span(out, &delimiter, &node.children, &delimiter, context)?;
1585        }
1586        Inline::Delete(node) => write_span(out, "~~", &node.children, "~~", context)?,
1587        Inline::Mark(node) => write_span(out, "==", &node.children, "==", context)?,
1588        Inline::InlineFootnote(node) => write_span(out, "^[", &node.children, "]", context)?,
1589        Inline::Shortcode(node) => {
1590            out.push(':');
1591            out.push_str(&node.name);
1592            out.push(':');
1593        }
1594        Inline::Tag(node) => {
1595            out.push('#');
1596            out.push_str(&node.name);
1597        }
1598        Inline::Code(node) => write_code_span(node, out),
1599        Inline::Link(node) => {
1600            write_span(out, "[", &node.children, "](", context)?;
1601            out.push_opaque(|out| {
1602                write_resource(
1603                    out,
1604                    &node.destination,
1605                    node.destination_kind,
1606                    node.title.as_ref(),
1607                );
1608            });
1609        }
1610        Inline::Autolink(node) => out.push_opaque(|out| match node.form {
1611            AutolinkForm::Literal => out.push_str(&node.text),
1612            AutolinkForm::Angle => {
1613                out.push('<');
1614                out.push_str(&node.text);
1615                out.push('>');
1616            }
1617        }),
1618        Inline::Image(node) => {
1619            write_span(out, "![", &node.alt, "](", context)?;
1620            out.push_opaque(|out| {
1621                write_resource(
1622                    out,
1623                    &node.destination,
1624                    node.destination_kind,
1625                    node.title.as_ref(),
1626                );
1627            });
1628        }
1629        Inline::LinkReference(node) => {
1630            write_reference(out, "[", &node.children, node.kind, &node.label, context)?;
1631        }
1632        Inline::ImageReference(node) => {
1633            write_reference(out, "![", &node.alt, node.kind, &node.label, context)?;
1634        }
1635        Inline::Html(node) => out.push_opaque(|out| out.push_str(&node.value)),
1636        Inline::SoftBreak(_) if context.heading => out.push(' '),
1637        Inline::SoftBreak(_) => out.push('\n'),
1638        Inline::LineBreak(node) => match node.kind {
1639            LineBreakKind::Backslash => out.push_str("\\\n"),
1640            LineBreakKind::Spaces => out.push_str("  \n"),
1641        },
1642        Inline::Math(node) => {
1643            let math = write_inline_math(node);
1644            out.push_opaque(|out| out.push_str(&math));
1645        }
1646        Inline::FootnoteReference(node) => {
1647            out.push_str("[^");
1648            out.push_str(&node.label);
1649            out.push(']');
1650        }
1651        Inline::WikiLink(node) => out.push_opaque(|out| {
1652            if node.embed {
1653                out.push('!');
1654            }
1655            out.push_str("[[");
1656            out.push_str(&node.target);
1657            if node.target != node.label {
1658                out.push('|');
1659                out.push_str(&node.label);
1660            }
1661            out.push_str("]]");
1662        }),
1663        Inline::TextDirective(node) => {
1664            // The label is read in an inline pass of its own.
1665            let label = if node.label.is_empty() {
1666                String::new()
1667            } else {
1668                format!("[{}]", write_inlines(&node.label, context)?)
1669            };
1670            out.push_opaque(|out| {
1671                out.push(':');
1672                out.push_str(&node.name);
1673                out.push_str(&label);
1674                out.push_str(&write_attributes(&node.attributes));
1675            });
1676        }
1677    }
1678    Ok(())
1679}
1680
1681fn emphasis_delimiter(delimiter: EmphasisDelimiter) -> &'static str {
1682    match delimiter {
1683        EmphasisDelimiter::Asterisk => "*",
1684        EmphasisDelimiter::Underscore => "_",
1685    }
1686}
1687
1688/// Writes `open`, `children`, and `close`.
1689fn write_span(
1690    out: &mut InlineOut,
1691    open: &str,
1692    children: &[Inline],
1693    close: &str,
1694    context: Context,
1695) -> Result<(), SerializeError> {
1696    out.push_str(open);
1697    for child in children {
1698        write_inline(child, context, out)?;
1699    }
1700    out.push_str(close);
1701    Ok(())
1702}
1703
1704/// A link's or image's `destination "title")`, after its `](`.
1705fn write_resource(
1706    out: &mut String,
1707    destination: &str,
1708    kind: LinkDestinationKind,
1709    title: Option<&Title>,
1710) {
1711    out.push_str(&write_destination(destination, kind));
1712    if let Some(title) = title {
1713        out.push(' ');
1714        out.push_str(&write_title(title));
1715    }
1716    out.push(')');
1717}
1718
1719/// What follows a reference's text: nothing, `[]`, or `[label]`.
1720/// A reference link or image. A full reference writes its text and then its
1721/// label; a shortcut or collapsed one writes its label as written, which is
1722/// both its text and its key, so the text it reads back as matches the
1723/// definition it names.
1724fn write_reference(
1725    out: &mut InlineOut,
1726    open: &str,
1727    children: &[Inline],
1728    kind: ReferenceKind,
1729    label: &str,
1730    context: Context,
1731) -> Result<(), SerializeError> {
1732    match kind {
1733        ReferenceKind::Full => write_span(out, open, children, "]", context)?,
1734        ReferenceKind::Shortcut | ReferenceKind::Collapsed => {
1735            out.push_str(open);
1736            out.push_str(label);
1737            out.push(']');
1738        }
1739    }
1740    out.push_opaque(|out| write_reference_kind(out, kind, label));
1741    Ok(())
1742}
1743
1744fn write_reference_kind(out: &mut String, kind: ReferenceKind, label: &str) {
1745    match kind {
1746        ReferenceKind::Shortcut => {}
1747        ReferenceKind::Collapsed => out.push_str("[]"),
1748        ReferenceKind::Full => {
1749            out.push('[');
1750            // A label is matched as written.
1751            out.push_str(label);
1752            out.push(']');
1753        }
1754    }
1755}
1756
1757/// Writes a code span from its value: fenced by the shortest backtick run
1758/// that neither the value holds nor a run written before it in the same
1759/// inline content opens, and padded with a space at each end when the value
1760/// would otherwise lose an end to the fence or to the space stripping.
1761fn write_code_span(node: &CodeInline, out: &mut InlineOut) {
1762    let value = &node.value;
1763    out.scan_open_runs();
1764    let held = backtick_runs(value);
1765    // An escaped backtick written just before joins the opening fence into
1766    // one longer run, which an earlier open run of that length would take as
1767    // its close.
1768    let before = out.len() - out.trim_end_matches('`').len();
1769    let length = (1..)
1770        .find(|length| {
1771            !held.get(*length).copied().unwrap_or(false)
1772                && !out.is_open_run(*length)
1773                && (before == 0 || !out.is_open_run(before + *length))
1774        })
1775        .unwrap_or(1);
1776    let fence = "`".repeat(length);
1777    out.push_str(&fence);
1778    if code_span_needs_padding(value) {
1779        out.push(' ');
1780        out.push_str(value);
1781        out.push(' ');
1782    } else {
1783        out.push_str(value);
1784    }
1785    out.push_str(&fence);
1786    // The span's own runs are matched; what follows is read afresh.
1787    out.scanned = out.text.len();
1788}
1789
1790/// `runs[n]`: whether `value` holds a maximal run of `n` backticks.
1791fn backtick_runs(value: &str) -> Vec<bool> {
1792    // A value of `len` bytes holds runs of at most `len` backticks.
1793    let mut runs = alloc::vec![false; value.len() + 1];
1794    let mut run = 0;
1795    for byte in value.bytes() {
1796        if byte == b'`' {
1797            run += 1;
1798        } else {
1799            runs[run] = true;
1800            run = 0;
1801        }
1802    }
1803    runs[run] = true;
1804    runs
1805}
1806
1807fn code_span_needs_padding(input: &str) -> bool {
1808    input.starts_with('`')
1809        || input.ends_with('`')
1810        || (input.starts_with(' ') && input.ends_with(' ') && input.chars().any(|char| char != ' '))
1811}
1812
1813pub(crate) fn write_inline_math(node: &MathInline) -> String {
1814    match node.kind {
1815        MathInlineKind::Code => format!("$`{}`$", node.value),
1816        // Dollar math is written verbatim behind its exact-length fence.
1817        MathInlineKind::Dollar { dollars } => {
1818            let fence = "$".repeat(usize::from(dollars));
1819            format!("{fence}{}{fence}", node.value)
1820        }
1821    }
1822}
1823
1824/// A directive's `[label]`, written as the text it keeps if any, or nothing
1825/// for an empty label.
1826fn write_directive_label(label: &[Inline], source: Option<&str>) -> Result<String, SerializeError> {
1827    if let Some(text) = one_line(source, true) {
1828        return Ok(if text.is_empty() {
1829            String::new()
1830        } else {
1831            format!("[{text}]")
1832        });
1833    }
1834    if label.is_empty() {
1835        Ok(String::new())
1836    } else {
1837        Ok(format!("[{}]", write_inlines(label, Context::BLOCK)?))
1838    }
1839}
1840
1841/// The text a leaf keeps, where it can be written in place of its inlines:
1842/// on one line when `single` holds, as in a cell, a label, or an ATX heading,
1843/// which a line ending would end.
1844fn one_line(source: Option<&str>, single: bool) -> Option<&str> {
1845    source.filter(|text| !single || !text.contains(['\n', '\r']))
1846}
1847
1848fn write_attributes(attributes: &[DirectiveAttribute]) -> String {
1849    if attributes.is_empty() {
1850        return String::new();
1851    }
1852    let mut output = String::from("{");
1853    for (index, attribute) in attributes.iter().enumerate() {
1854        if index > 0 {
1855            output.push(' ');
1856        }
1857        match (&*attribute.name, &attribute.value) {
1858            ("id", Some(value)) if is_directive_shorthand_value(value) => {
1859                output.push('#');
1860                output.push_str(value);
1861            }
1862            ("class", Some(value)) if is_directive_shorthand_value(value) => {
1863                output.push('.');
1864                output.push_str(value);
1865            }
1866            (_, Some(value)) => {
1867                output.push_str(&attribute.name);
1868                output.push_str("=\"");
1869                output.push_str(&escape_title(value, LinkTitleKind::DoubleQuote));
1870                output.push('"');
1871            }
1872            (_, None) => output.push_str(&attribute.name),
1873        }
1874    }
1875    output.push('}');
1876    output
1877}
1878
1879fn is_directive_shorthand_value(input: &str) -> bool {
1880    !input.is_empty()
1881        && input
1882            .chars()
1883            .all(|char| char.is_ascii_alphanumeric() || matches!(char, '_' | '-'))
1884}
1885
1886fn write_destination(input: &str, kind: LinkDestinationKind) -> String {
1887    match kind {
1888        LinkDestinationKind::Omitted if input.is_empty() => String::new(),
1889        LinkDestinationKind::Angle => {
1890            let mut output = String::from("<");
1891            for char in input.chars() {
1892                match char {
1893                    char if char.is_control() => output.push_str(&char_reference(char)),
1894                    '\\' | '<' | '>' => {
1895                        output.push('\\');
1896                        output.push(char);
1897                    }
1898                    _ => output.push(char),
1899                }
1900            }
1901            output.push('>');
1902            output
1903        }
1904        LinkDestinationKind::Bare | LinkDestinationKind::Omitted if input.is_empty() => "<>".into(),
1905        LinkDestinationKind::Bare | LinkDestinationKind::Omitted => {
1906            let mut output = String::new();
1907            for char in input.chars() {
1908                match char {
1909                    // A space would end the destination, and `\ ` is no escape.
1910                    char if char.is_control() || char == ' ' => {
1911                        output.push_str(&char_reference(char));
1912                    }
1913                    '(' | ')' | '\\' | '<' | '>' | '&' => {
1914                        output.push('\\');
1915                        output.push(char);
1916                    }
1917                    _ => output.push(char),
1918                }
1919            }
1920            output
1921        }
1922    }
1923}
1924
1925fn write_title(title: &Title) -> String {
1926    let (open, close) = match title.kind {
1927        LinkTitleKind::DoubleQuote => ('"', '"'),
1928        LinkTitleKind::SingleQuote => ('\'', '\''),
1929        LinkTitleKind::Paren => ('(', ')'),
1930    };
1931    let mut output = String::new();
1932    output.push(open);
1933    output.push_str(&escape_title(&title.value, title.kind));
1934    output.push(close);
1935    output
1936}
1937
1938fn escape_title(input: &str, kind: LinkTitleKind) -> String {
1939    let mut output = String::new();
1940    for char in input.chars() {
1941        match char {
1942            char if char.is_control() => output.push_str(&char_reference(char)),
1943            '\\' | '&' => {
1944                output.push('\\');
1945                output.push(char);
1946            }
1947            '"' if kind == LinkTitleKind::DoubleQuote => output.push_str("\\\""),
1948            '\'' if kind == LinkTitleKind::SingleQuote => output.push_str("\\'"),
1949            '(' | ')' if kind == LinkTitleKind::Paren => {
1950                output.push('\\');
1951                output.push(char);
1952            }
1953            _ => output.push(char),
1954        }
1955    }
1956    output
1957}
1958
1959/// Prefixes every line of `input` with a container's `prefix`, keeping each
1960/// line's ending; an empty line takes the prefix without its trailing
1961/// whitespace, so that no blank line the writer writes ends in whitespace.
1962fn prefix_container_lines(input: &str, prefix: &str) -> String {
1963    prefix_lines_with(input, prefix, prefix.trim_end_matches([' ', '\t']))
1964}
1965
1966/// Prefixes every line of `input` with `prefix`, keeping each line's ending.
1967fn prefix_lines(input: &str, prefix: &str) -> String {
1968    prefix_lines_with(input, prefix, prefix)
1969}
1970
1971/// Prefixes every line of `input` with `prefix`, or an empty one with
1972/// `empty_prefix`, keeping each line's ending.
1973fn prefix_lines_with(input: &str, prefix: &str, empty_prefix: &str) -> String {
1974    if input.is_empty() {
1975        return String::new();
1976    }
1977    let bytes = input.as_bytes();
1978    let mut output = String::new();
1979    let mut line_start = 0;
1980    let mut cursor = 0;
1981    while cursor < input.len() {
1982        let eol_end = match bytes[cursor] {
1983            b'\n' => Some(cursor + 1),
1984            b'\r' if bytes.get(cursor + 1) == Some(&b'\n') => Some(cursor + 2),
1985            b'\r' => Some(cursor + 1),
1986            _ => None,
1987        };
1988        if let Some(end) = eol_end {
1989            output.push_str(if cursor == line_start {
1990                empty_prefix
1991            } else {
1992                prefix
1993            });
1994            output.push_str(&input[line_start..end]);
1995            cursor = end;
1996            line_start = cursor;
1997        } else {
1998            cursor += 1;
1999        }
2000    }
2001    if line_start < input.len() {
2002        output.push_str(prefix);
2003        output.push_str(&input[line_start..]);
2004    }
2005    output
2006}
2007
2008fn indent_after_first_line(input: &str, width: usize) -> String {
2009    indent_lines_of(input, width)
2010}
2011
2012fn indent_continuation(input: &str) -> String {
2013    indent_lines_of(input, 4)
2014}
2015
2016/// Indents every line of `input` after its first by `width` spaces, as a
2017/// container's continuation; an empty line stays empty. Each line keeps its
2018/// line ending, a final one included.
2019fn indent_lines_of(input: &str, width: usize) -> String {
2020    let mut output = String::with_capacity(input.len());
2021    let bytes = input.as_bytes();
2022    let mut line_start = 0;
2023    let mut index = 0;
2024    let push = |output: &mut String, line: &str, number: usize| {
2025        if number > 0 && !line.starts_with(['\n', '\r']) {
2026            output.extend(core::iter::repeat_n(' ', width));
2027        }
2028        output.push_str(line);
2029    };
2030    let mut number = 0;
2031    while index < bytes.len() {
2032        let end = match bytes[index] {
2033            b'\n' => Some(index + 1),
2034            b'\r' if bytes.get(index + 1) == Some(&b'\n') => Some(index + 2),
2035            b'\r' => Some(index + 1),
2036            _ => None,
2037        };
2038        match end {
2039            Some(end) => {
2040                push(&mut output, &input[line_start..end], number);
2041                number += 1;
2042                index = end;
2043                line_start = end;
2044            }
2045            None => index += 1,
2046        }
2047    }
2048    if line_start < input.len() {
2049        push(&mut output, &input[line_start..], number);
2050    }
2051    output
2052}
2053
2054fn trim_trailing_newline(input: &str) -> &str {
2055    input.trim_end_matches('\n').trim_end_matches('\r')
2056}
2057
2058fn ends_with_line_ending(input: &str) -> bool {
2059    input.ends_with('\n') || input.ends_with('\r')
2060}
2061
2062/// The fence for a code block's `value` and the columns the block is indented
2063/// by, so that no line of `value` closes it. A closing fence is up to three
2064/// spaces, at least as many marker chars, and nothing else but spaces and
2065/// tabs. The fence keeps `min_len` chars when indenting the block (which the
2066/// value's lines lose again) moves every closing-like line past three spaces;
2067/// otherwise it grows past the longest of them.
2068fn code_block_fence(value: &str, marker: FenceMarker, min_len: usize) -> (String, usize) {
2069    let char = match marker {
2070        FenceMarker::Backtick => '`',
2071        FenceMarker::Tilde => '~',
2072    };
2073    let closing_like = |line: &str, length: usize| {
2074        let indent = line.len() - line.trim_start_matches(' ').len();
2075        let rest = &line[indent..];
2076        let run = rest.len() - rest.trim_start_matches(char).len();
2077        (indent <= 3 && run >= length && rest[run..].trim_matches([' ', '\t']).is_empty())
2078            .then_some((indent, run))
2079    };
2080    let lines = || value.split(['\n', '\r']);
2081    let least_indent = lines()
2082        .filter_map(|line| closing_like(line, min_len))
2083        .map(|(indent, _)| indent)
2084        .min();
2085    match least_indent {
2086        None => (char.to_string().repeat(min_len), 0),
2087        Some(indent) if indent > 0 => (char.to_string().repeat(min_len), 4 - indent),
2088        Some(_) => {
2089            let mut length = min_len;
2090            for line in lines() {
2091                if let Some((_, run)) = closing_like(line, length) {
2092                    length = run + 1;
2093                }
2094            }
2095            (char.to_string().repeat(length), 0)
2096        }
2097    }
2098}
2099
2100fn block_math_fence(input: &str) -> String {
2101    let mut length = 2;
2102    for line in trim_trailing_newline(input).lines() {
2103        let trimmed = line.trim();
2104        if trimmed.len() >= 2 && trimmed.chars().all(|char| char == '$') {
2105            length = length.max(trimmed.len() + 1);
2106        }
2107    }
2108    "$".repeat(length)
2109}
2110
2111fn directive_fence(inner: &str) -> String {
2112    let mut length = 3;
2113    for line in inner.lines() {
2114        if let Some(closing) = directive_closing_fence_len(line) {
2115            length = length.max(closing + 1);
2116        }
2117    }
2118    ":".repeat(length)
2119}
2120
2121fn directive_closing_fence_len(line: &str) -> Option<usize> {
2122    let trimmed = trim_up_to_three_indent_columns(line)?;
2123    let length = trimmed
2124        .as_bytes()
2125        .iter()
2126        .take_while(|byte| **byte == b':')
2127        .count();
2128    if length >= 3 && trimmed[length..].trim().is_empty() {
2129        Some(length)
2130    } else {
2131        None
2132    }
2133}
2134
2135fn trim_up_to_three_indent_columns(input: &str) -> Option<&str> {
2136    let mut columns = 0usize;
2137    let mut bytes = 0usize;
2138    for byte in input.as_bytes() {
2139        match *byte {
2140            b' ' => columns += 1,
2141            b'\t' => columns += 4 - (columns % 4),
2142            _ => break,
2143        }
2144        bytes += 1;
2145    }
2146    (columns <= 3).then_some(&input[bytes..])
2147}
2148
2149#[cfg(test)]
2150mod tests {
2151    use super::*;
2152
2153    // serialization: Unchanged source is kept
2154    #[test]
2155    fn the_read_back_check_names_each_block_that_reads_differently() {
2156        let blocks = crate::parse("> x\n\n# y\n\nz").document.children;
2157        assert_eq!(misread(&blocks, "> x\n\n# y\n\nz", &[0, 2]), [0usize; 0]);
2158        // A block read differently in its place.
2159        assert_eq!(misread(&blocks, "> w\n\n# y\n\nz", &[0, 2]), [0]);
2160        assert_eq!(misread(&blocks, "> w\n\n# y\n\nz", &[2]), [0usize; 0]);
2161        // `z` read into the quote: the block count differs.
2162        assert_eq!(misread(&blocks, "> x\nz\n\n# y", &[0, 2]), [0, 2]);
2163    }
2164}