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