mdwright_format/options.rs
1/// Formatter knobs.
2///
3/// Style knobs default to `Preserve` except GFM tables, which default
4/// to compact normal form. The structural-emit pipeline never consults
5/// style knobs; rewrites are applied by the formatter's rewrite-family
6/// pipeline. Structural defaults: `wrap = keep`, `trailing-newline =
7/// preserve`, `end-of-line = lf`, empty exclude list.
8#[derive(Debug, Clone)]
9pub struct FmtOptions {
10 wrap: Wrap,
11 wrap_strategy: WrapStrategy,
12 italic: ItalicStyle,
13 strong: StrongStyle,
14 list_marker: ListMarkerStyle,
15 list_continuation_indent: ListContinuationIndent,
16 ordered_list: OrderedListStyle,
17 table: TableStyle,
18 trailing_newline: TrailingNewline,
19 end_of_line: EndOfLine,
20 exclude_globs: Vec<String>,
21 link_def_placement: Placement,
22 link_def_style: LinkDefStyle,
23 footnote_placement: Placement,
24 preserve_frontmatter: bool,
25 thematic_break_style: ThematicStyle,
26 math: MathOptions,
27 heading_attrs: HeadingAttrsStyle,
28}
29
30/// Heading-attribute trailer emission policy.
31///
32/// `# Heading {#id .class key=val}` parses (with
33/// `Options::ENABLE_HEADING_ATTRIBUTES`) into a `Tag::Heading` carrying
34/// the parsed `id`, `classes`, and `attrs`. This knob decides how the
35/// formatter re-emits the trailer.
36///
37/// - [`Self::Preserve`] (default): emit the source trailer byte-verbatim
38/// between the rendered inline body and the line terminator. Matches
39/// the preserve-by-default ethos every other style knob defaults to.
40/// - [`Self::Canonicalise`]: emit `{#id .class₁ .class₂ k=val}` in a
41/// fixed order: id first, then classes in source order, then
42/// key=value pairs in source order. Matches mdformat-mkdocs canonical.
43#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
44pub enum HeadingAttrsStyle {
45 /// Emit the source trailer byte-verbatim.
46 #[default]
47 Preserve,
48 /// Emit the trailer in fixed canonical order: `#id`, then classes
49 /// (source order), then `key=value` pairs (source order).
50 Canonicalise,
51}
52
53/// Math canonicalisation configuration.
54///
55/// All fields are off by default. Math regions are opaque to
56/// `CommonMark`: pulldown-cmark parses their bytes as prose, so any
57/// whitespace change inside shifts the byte-level event stream. Authors
58/// who render math downstream (`KaTeX`, `MathJax`) opt in.
59#[derive(Copy, Clone, Debug, Default)]
60pub struct MathOptions {
61 /// Whether whole-block math regions (display `\[…\]` / `$$…$$`
62 /// and environments standing alone) are normalised.
63 pub normalise: bool,
64 /// How math regions are emitted for downstream renderers. See
65 /// [`MathRender`] for the modes; default is [`MathRender::None`]
66 /// (verbatim emission, today's behaviour).
67 pub render: MathRender,
68}
69
70/// Delimiter rewrite policy for math regions at emit time.
71///
72/// mdwright never typesets math itself; downstream renderers
73/// (`KaTeX`, `MathJax`, `mkdocs-material`'s math plugin) do that. The
74/// modes here determine the *shape* of the math regions in the
75/// formatted output so the downstream renderer recognises them.
76///
77/// - [`Self::None`] (default): pass math regions through verbatim;
78/// today's behaviour.
79/// - [`Self::CommonmarkKatex`]: same emission as `None`, but signals
80/// intent in build logs. The bracket/paren forms (`\[…\]`, `\(…\)`)
81/// and dollar forms (`$$…$$`, `$…$`) are both recognised by `KaTeX`
82/// and `MathJax` v3 auto-renderers without rewriting.
83/// - [`Self::Dollar`]: rewrite `\[ … \]` to `$$ … $$` and `\( … \)`
84/// to `$ … $` at emit time. LaTeX environments
85/// (`\begin{align*}…\end{align*}`) are not rewritten; there is no
86/// dollar form of an environment.
87#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
88pub enum MathRender {
89 /// Pass math regions through verbatim.
90 #[default]
91 None,
92 /// Pass through verbatim; greppable signal that downstream is
93 /// `KaTeX` or `MathJax` over `CommonMark`-shaped math.
94 CommonmarkKatex,
95 /// Rewrite backslash-bracket / backslash-paren math to dollar
96 /// form. Environments are left unchanged.
97 Dollar,
98}
99
100impl FmtOptions {
101 /// Wrap mode for prose paragraphs.
102 #[must_use]
103 pub fn wrap(&self) -> Wrap {
104 self.wrap
105 }
106
107 /// Paragraph wrap strategy.
108 #[must_use]
109 pub fn wrap_strategy(&self) -> WrapStrategy {
110 self.wrap_strategy
111 }
112
113 /// Italic delimiter normalisation policy.
114 #[must_use]
115 pub fn italic(&self) -> ItalicStyle {
116 self.italic
117 }
118
119 /// Strong-emphasis delimiter normalisation policy.
120 #[must_use]
121 pub fn strong(&self) -> StrongStyle {
122 self.strong
123 }
124
125 /// Unordered-list bullet normalisation policy.
126 #[must_use]
127 pub fn list_marker(&self) -> ListMarkerStyle {
128 self.list_marker
129 }
130
131 /// List continuation indentation used when wrapping list-item paragraphs.
132 #[must_use]
133 pub fn list_continuation_indent(&self) -> ListContinuationIndent {
134 self.list_continuation_indent
135 }
136
137 /// Ordered-list number normalisation policy.
138 #[must_use]
139 pub fn ordered_list(&self) -> OrderedListStyle {
140 self.ordered_list
141 }
142
143 /// GFM table canonicalisation policy.
144 #[must_use]
145 pub fn table(&self) -> TableStyle {
146 self.table
147 }
148
149 /// Trailing-newline policy at the document boundary.
150 #[must_use]
151 pub fn trailing_newline(&self) -> TrailingNewline {
152 self.trailing_newline
153 }
154
155 /// Line-ending normalisation policy.
156 #[must_use]
157 pub fn end_of_line(&self) -> EndOfLine {
158 self.end_of_line
159 }
160
161 /// Formatter-specific exclude globs (independent of `[lint]
162 /// exclude`).
163 #[must_use]
164 pub fn exclude_globs(&self) -> &[String] {
165 &self.exclude_globs
166 }
167
168 /// Where reference-link definitions are emitted: gathered + sorted
169 /// at end of document, or kept in source positions.
170 #[must_use]
171 pub fn link_def_placement(&self) -> Placement {
172 self.link_def_placement
173 }
174
175 /// Whether reference-link and inline-link destinations are emitted
176 /// bare or angle-bracketed.
177 #[must_use]
178 pub fn link_def_style(&self) -> LinkDefStyle {
179 self.link_def_style
180 }
181
182 /// Where footnote definitions are emitted. Populated for parity
183 /// with [`Self::link_def_placement`]; session 11 reads this.
184 #[must_use]
185 pub fn footnote_placement(&self) -> Placement {
186 self.footnote_placement
187 }
188
189 /// Whether to emit the document's frontmatter byte-verbatim in
190 /// the formatted output. `true` (the default) preserves YAML or
191 /// TOML frontmatter exactly; `false` strips it.
192 #[must_use]
193 pub fn preserve_frontmatter(&self) -> bool {
194 self.preserve_frontmatter
195 }
196
197 /// Thematic-break canonicalisation policy. Defaults to
198 /// [`ThematicStyle::Preserve`]. The structural-emit path does not
199 /// consult this; a later canonicalisation post-pass will.
200 #[must_use]
201 pub fn thematic_break_style(&self) -> ThematicStyle {
202 self.thematic_break_style
203 }
204
205 /// Math canonicalisation configuration. See [`MathOptions`] for the
206 /// reason every field defaults off.
207 #[must_use]
208 pub fn math(&self) -> MathOptions {
209 self.math
210 }
211
212 /// Override the math options. Returns the receiver for chaining.
213 #[must_use]
214 pub fn with_math(mut self, math: MathOptions) -> Self {
215 self.math = math;
216 self
217 }
218
219 /// Override only the math render policy. Composes with the
220 /// existing [`MathOptions`]; preserves `normalise`.
221 #[must_use]
222 pub fn with_math_render(mut self, render: MathRender) -> Self {
223 self.math.render = render;
224 self
225 }
226
227 /// Heading-attribute trailer emission policy.
228 #[must_use]
229 pub fn heading_attrs(&self) -> HeadingAttrsStyle {
230 self.heading_attrs
231 }
232
233 /// Override the heading-attribute style.
234 #[must_use]
235 pub fn with_heading_attrs(mut self, style: HeadingAttrsStyle) -> Self {
236 self.heading_attrs = style;
237 self
238 }
239
240 /// Resolve the italic delimiter to emit, given the byte the
241 /// source originally used (`b'*'` or `b'_'`). `Preserve` returns
242 /// `source_delim`; fixed styles return their own byte. Keeps the
243 /// `match` out of every render site.
244 #[must_use]
245 pub fn resolve_italic(&self, source_delim: u8) -> u8 {
246 match self.italic {
247 ItalicStyle::Asterisk => b'*',
248 ItalicStyle::Underscore => b'_',
249 ItalicStyle::Preserve => source_delim,
250 }
251 }
252
253 /// Override the wrap mode. Returns the receiver for chaining.
254 /// Used by callers that build [`FmtOptions`] programmatically
255 /// (benches, golden tests, `--wrap` overrides).
256 #[must_use]
257 pub fn with_wrap(mut self, wrap: Wrap) -> Self {
258 self.wrap = wrap;
259 self
260 }
261
262 /// Override the paragraph wrap strategy.
263 #[must_use]
264 pub fn with_wrap_strategy(mut self, strategy: WrapStrategy) -> Self {
265 self.wrap_strategy = strategy;
266 self
267 }
268
269 /// Override the italic style. Used by callers that build options
270 /// programmatically (property tests, CLI overrides).
271 #[must_use]
272 pub fn with_italic(mut self, italic: ItalicStyle) -> Self {
273 self.italic = italic;
274 self
275 }
276
277 /// Override the strong style.
278 #[must_use]
279 pub fn with_strong(mut self, strong: StrongStyle) -> Self {
280 self.strong = strong;
281 self
282 }
283
284 /// Override the unordered-list bullet style.
285 #[must_use]
286 pub fn with_list_marker(mut self, list_marker: ListMarkerStyle) -> Self {
287 self.list_marker = list_marker;
288 self
289 }
290
291 /// Override list continuation indentation for wrapped list items.
292 #[must_use]
293 pub fn with_list_continuation_indent(mut self, indent: ListContinuationIndent) -> Self {
294 self.list_continuation_indent = indent;
295 self
296 }
297
298 /// Override the ordered-list numbering policy.
299 #[must_use]
300 pub fn with_ordered_list(mut self, ordered_list: OrderedListStyle) -> Self {
301 self.ordered_list = ordered_list;
302 self
303 }
304
305 /// Override the GFM table canonicalisation policy.
306 #[must_use]
307 pub fn with_table(mut self, table: TableStyle) -> Self {
308 self.table = table;
309 self
310 }
311
312 /// Override the thematic-break canonicalisation policy.
313 #[must_use]
314 pub fn with_thematic_break(mut self, thematic_break: ThematicStyle) -> Self {
315 self.thematic_break_style = thematic_break;
316 self
317 }
318
319 /// Override the link-destination style.
320 #[must_use]
321 pub fn with_link_def_style(mut self, link_def_style: LinkDefStyle) -> Self {
322 self.link_def_style = link_def_style;
323 self
324 }
325
326 /// Resolve the unordered-list bullet to emit, given the byte the
327 /// source used (`b'-'`, `b'*'`, or `b'+'`).
328 #[must_use]
329 pub fn resolve_list_marker(&self, source_marker: u8) -> u8 {
330 match self.list_marker {
331 ListMarkerStyle::Dash => b'-',
332 ListMarkerStyle::Asterisk => b'*',
333 ListMarkerStyle::Plus => b'+',
334 ListMarkerStyle::Preserve => source_marker,
335 }
336 }
337
338 // ----- Canonicalisation post-pass targets ---------------------
339 //
340 // Each accessor below maps a style knob to `Some(target)` when the
341 // user opted into canonicalisation and `None` when the knob is
342 // `Preserve`. Rewrite-family producers are the only
343 // consumers; identity emit never reads these.
344
345 /// Italic-delimiter target byte. `None` keeps source bytes.
346 #[must_use]
347 pub(crate) fn italic_target_byte(&self) -> Option<u8> {
348 match self.italic {
349 ItalicStyle::Asterisk => Some(b'*'),
350 ItalicStyle::Underscore => Some(b'_'),
351 ItalicStyle::Preserve => None,
352 }
353 }
354
355 /// Strong-delimiter target byte. `None` keeps source bytes.
356 #[must_use]
357 pub(crate) fn strong_target_byte(&self) -> Option<u8> {
358 match self.strong {
359 StrongStyle::Asterisk => Some(b'*'),
360 StrongStyle::Underscore => Some(b'_'),
361 StrongStyle::Preserve => None,
362 }
363 }
364
365 /// Unordered-list bullet target byte. `None` keeps source bytes.
366 #[must_use]
367 pub(crate) fn list_marker_target_byte(&self) -> Option<u8> {
368 match self.list_marker {
369 ListMarkerStyle::Dash => Some(b'-'),
370 ListMarkerStyle::Asterisk => Some(b'*'),
371 ListMarkerStyle::Plus => Some(b'+'),
372 ListMarkerStyle::Preserve => None,
373 }
374 }
375
376 /// Ordered-list renumbering policy for the canonicalisation pass.
377 #[must_use]
378 pub(crate) fn ordered_list_target(&self) -> Option<OrderedListStyle> {
379 match self.ordered_list {
380 OrderedListStyle::Consistent | OrderedListStyle::One => Some(self.ordered_list),
381 OrderedListStyle::Preserve => None,
382 }
383 }
384
385 /// Thematic-break shape for the canonicalisation pass.
386 #[must_use]
387 pub(crate) fn thematic_target(&self) -> Option<ThematicStyle> {
388 match self.thematic_break_style {
389 ThematicStyle::Dash | ThematicStyle::Asterisk | ThematicStyle::Underscore | ThematicStyle::Underscore70 => {
390 Some(self.thematic_break_style)
391 }
392 ThematicStyle::Preserve => None,
393 }
394 }
395
396 /// `true` iff GFM tables should be normalised.
397 #[must_use]
398 pub(crate) fn should_normalise_tables(&self) -> bool {
399 !matches!(self.table, TableStyle::Preserve)
400 }
401
402 /// True iff a canonicalisation family other than tables is active.
403 ///
404 /// Tables default to compact normalisation, so callers use this to
405 /// preserve the identity fast path for table-free documents.
406 #[must_use]
407 pub(crate) fn has_non_table_canonicalisation(&self) -> bool {
408 self.italic_target_byte().is_some()
409 || self.strong_target_byte().is_some()
410 || self.list_marker_target_byte().is_some()
411 || self.thematic_target().is_some()
412 || self.ordered_list_target().is_some()
413 || self.link_def_target().is_some()
414 || matches!(self.heading_attrs, HeadingAttrsStyle::Canonicalise)
415 || matches!(self.math.render, MathRender::Dollar)
416 || self.math.normalise
417 || !self.preserve_frontmatter
418 }
419
420 /// Link-destination angle-bracket rewrite target. `None` keeps
421 /// source form per definition.
422 #[must_use]
423 pub(crate) fn link_def_target(&self) -> Option<LinkDefStyle> {
424 match self.link_def_style {
425 LinkDefStyle::Bare => Some(LinkDefStyle::Bare),
426 LinkDefStyle::Angle => Some(LinkDefStyle::Angle),
427 LinkDefStyle::Preserve => None,
428 }
429 }
430
431 /// True iff any style knob is set to a non-`Preserve` value. When
432 /// false, the canonicalisation pass is skipped entirely.
433 #[must_use]
434 pub(crate) fn has_any_canonicalisation(&self) -> bool {
435 self.should_normalise_tables() || self.has_non_table_canonicalisation()
436 }
437
438 /// Override the trailing-newline policy.
439 #[must_use]
440 pub fn with_trailing_newline(mut self, trailing_newline: TrailingNewline) -> Self {
441 self.trailing_newline = trailing_newline;
442 self
443 }
444
445 /// Override the end-of-line policy.
446 #[must_use]
447 pub fn with_end_of_line(mut self, end_of_line: EndOfLine) -> Self {
448 self.end_of_line = end_of_line;
449 self
450 }
451
452 /// Override formatter exclude globs.
453 #[must_use]
454 pub fn with_exclude_globs(mut self, exclude_globs: Vec<String>) -> Self {
455 self.exclude_globs = exclude_globs;
456 self
457 }
458
459 /// Override reference-definition placement.
460 #[must_use]
461 pub fn with_link_def_placement(mut self, placement: Placement) -> Self {
462 self.link_def_placement = placement;
463 self
464 }
465
466 /// Override footnote-definition placement.
467 #[must_use]
468 pub fn with_footnote_placement(mut self, placement: Placement) -> Self {
469 self.footnote_placement = placement;
470 self
471 }
472
473 /// Override frontmatter preservation.
474 #[must_use]
475 pub fn with_preserve_frontmatter(mut self, preserve_frontmatter: bool) -> Self {
476 self.preserve_frontmatter = preserve_frontmatter;
477 self
478 }
479}
480
481impl Default for FmtOptions {
482 fn default() -> Self {
483 Self {
484 wrap: Wrap::Keep,
485 wrap_strategy: WrapStrategy::Stable,
486 // Most style knobs default to Preserve so structural emit
487 // round-trips source bytes. GFM tables default to compact
488 // normal form because redundant cell padding is not useful
489 // source information.
490 italic: ItalicStyle::Preserve,
491 strong: StrongStyle::Preserve,
492 list_marker: ListMarkerStyle::Preserve,
493 list_continuation_indent: ListContinuationIndent::MarkerWidth,
494 ordered_list: OrderedListStyle::Preserve,
495 table: TableStyle::Compact,
496 trailing_newline: TrailingNewline::Preserve,
497 end_of_line: EndOfLine::Lf,
498 exclude_globs: Vec::new(),
499 link_def_placement: Placement::End,
500 link_def_style: LinkDefStyle::Preserve,
501 // Footnotes stay at their source position. Pulldown's HTML
502 // renderer emits the `<div class="footnote-definition">`
503 // block at the point of parsing, so moving definitions to
504 // the document tail under `Placement::End` would change the
505 // event stream.
506 footnote_placement: Placement::Preserve,
507 preserve_frontmatter: true,
508 thematic_break_style: ThematicStyle::Preserve,
509 math: MathOptions::default(),
510 heading_attrs: HeadingAttrsStyle::default(),
511 }
512 }
513}
514
515impl FmtOptions {
516 /// mdformat-compatible formatting profile where mdwright can
517 /// reproduce mdformat's spelling without weakening verification.
518 ///
519 /// mdformat 1.0 defaults to `wrap = keep`; callers that want a
520 /// column limit should still set [`Self::with_wrap`] explicitly.
521 #[must_use]
522 pub fn mdformat() -> Self {
523 Self::default()
524 .with_wrap_strategy(WrapStrategy::Stable)
525 .with_list_marker(ListMarkerStyle::Dash)
526 .with_list_continuation_indent(ListContinuationIndent::FourSpace)
527 .with_ordered_list(OrderedListStyle::One)
528 .with_thematic_break(ThematicStyle::Underscore70)
529 .with_table(TableStyle::Align)
530 }
531}
532
533/// Trailing-newline policy applied at the document boundary.
534///
535/// `Preserve` (the default) matches the source's trailing-newline
536/// shape: if the source ends with one or more `\n` bytes, the
537/// formatted output ends with exactly one `\n`; if the source has no
538/// trailing `\n`, the output has none either. This is what
539/// formatter validation needs to hold: pulldown-cmark's HTML render of
540/// `\t\x10` is `<pre><code>\x10</code></pre>` while its
541/// render of `\t\x10\n` is `<pre><code>\x10\n</code></pre>`; the
542/// trailing LF lives inside the code body for any document ending in
543/// an indented code block. An unconditional "ensure trailing `\n`"
544/// post-pass cannot avoid that class of HTML-divergence; `Preserve`
545/// avoids it by construction.
546///
547/// `Strip` drops every trailing `\n`. `Ensure` forces exactly one
548/// trailing `\n` (the pre-Preserve `trailing_newline = true`
549/// behaviour); kept under an explicit name so callers opt in to the
550/// foot-gun rather than getting it by default.
551#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
552pub enum TrailingNewline {
553 /// Match the source: one trailing `\n` iff the source had any.
554 #[default]
555 Preserve,
556 /// Drop every trailing `\n`.
557 Strip,
558 /// Force exactly one trailing `\n`, appending if absent.
559 Ensure,
560}
561
562/// Emission position for collected items (link reference definitions,
563/// footnote definitions). `End` (the default) gathers items and sorts
564/// them at the end of the document; `Preserve` keeps source order.
565#[derive(Copy, Clone, Debug, PartialEq, Eq)]
566pub enum Placement {
567 End,
568 Preserve,
569}
570
571/// Destination style for link reference definitions and inline links.
572///
573/// `Bare` emits `[label]: url`; `Angle` emits `[label]: <url>`;
574/// `Preserve` (the default) emits whichever form the source used for
575/// each destination.
576#[derive(Copy, Clone, Debug, PartialEq, Eq)]
577pub enum LinkDefStyle {
578 Bare,
579 Angle,
580 Preserve,
581}
582
583/// Prose-wrap mode.
584///
585/// `Keep` (default) and `No` both leave existing breaks alone; the
586/// distinction is whether the formatter is *allowed* to introduce new
587/// breaks later (`Keep` means "do nothing if already within budget",
588/// `No` means "never break"). `At(n)` wraps at column `n`.
589#[derive(Copy, Clone, Debug, PartialEq, Eq)]
590pub enum Wrap {
591 Keep,
592 No,
593 At(u32),
594}
595
596impl Wrap {
597 /// Effective column target. `Keep` and `No` both return
598 /// `u32::MAX`, signalling the wrap pass "do not introduce breaks".
599 #[must_use]
600 pub fn columns(self) -> u32 {
601 match self {
602 Self::Keep | Self::No => u32::MAX,
603 Self::At(n) => n,
604 }
605 }
606
607 /// Reduce the target column count by `n` for nested contexts whose
608 /// physical lines will be prefixed (blockquote `> `, list-item
609 /// marker, footnote indent). `Keep` and `No` are unaffected.
610 #[must_use]
611 pub fn shrink(self, n: u32) -> Self {
612 match self {
613 Self::At(c) => Self::At(c.saturating_sub(n).max(1)),
614 Self::Keep | Self::No => self,
615 }
616 }
617}
618
619/// Paragraph-wrap planner.
620#[derive(Copy, Clone, Debug, PartialEq, Eq)]
621pub enum WrapStrategy {
622 /// Reflow each hard-break-bounded run with mdformat-compatible soft breaks.
623 Stable,
624 /// Rebalance the whole paragraph with a squared-slack planner.
625 Balanced,
626}
627
628impl WrapStrategy {
629 /// TOML spelling for this strategy.
630 #[must_use]
631 pub fn as_str(self) -> &'static str {
632 match self {
633 Self::Stable => "stable",
634 Self::Balanced => "balanced",
635 }
636 }
637}
638
639/// Italic delimiter normalisation policy. Defaults to `Preserve`:
640/// structural emit preserves each run's source delimiter. Fixed
641/// variants are consumed only by the canonicalisation post-pass.
642#[derive(Copy, Clone, Debug, PartialEq, Eq)]
643pub enum ItalicStyle {
644 Asterisk,
645 Underscore,
646 Preserve,
647}
648
649/// Strong-emphasis delimiter normalisation policy. Defaults to
650/// `Preserve`. Independent of [`ItalicStyle`] so an author can
651/// canonicalise one without forcing the other.
652#[derive(Copy, Clone, Debug, PartialEq, Eq)]
653pub enum StrongStyle {
654 Asterisk,
655 Underscore,
656 Preserve,
657}
658
659/// Unordered-list bullet normalisation policy. Defaults to
660/// `Preserve`: structural emit keeps each list's source bullet.
661#[derive(Copy, Clone, Debug, PartialEq, Eq)]
662pub enum ListMarkerStyle {
663 Dash,
664 Asterisk,
665 Plus,
666 Preserve,
667}
668
669/// Continuation indentation for wrapped list-item paragraphs.
670#[derive(Copy, Clone, Debug, PartialEq, Eq)]
671pub enum ListContinuationIndent {
672 /// Continue at the marker width: `- text` wraps to ` text`.
673 MarkerWidth,
674 /// Continue with four spaces after the containing block prefix.
675 FourSpace,
676}
677
678/// Ordered-list number normalisation policy.
679///
680/// `One` rewrites markers to `1.` where verification preserves the
681/// list start. `Consistent` renumbers from the source first marker.
682/// `Preserve` keeps source numbering verbatim.
683#[derive(Copy, Clone, Debug, PartialEq, Eq)]
684pub enum OrderedListStyle {
685 /// Rewrite every item marker in the list to `1.`.
686 One,
687 /// Renumber items from the source list's first marker.
688 Consistent,
689 Preserve,
690}
691
692/// Thematic-break canonicalisation policy. Defaults to `Preserve`:
693/// structural emit echoes the source `---` / `***` / `___` line
694/// verbatim. The fixed variants exist for the future canonicalisation
695/// pass.
696#[derive(Copy, Clone, Debug, PartialEq, Eq)]
697pub enum ThematicStyle {
698 Dash,
699 Asterisk,
700 Underscore,
701 /// Rewrite the whole break line to mdformat's 70 underscores.
702 Underscore70,
703 Preserve,
704}
705
706impl ThematicStyle {
707 /// The repeated byte the thematic-break line is built from, when
708 /// the style names a single byte. `Preserve` returns `None`
709 /// because the byte to emit comes from the source itself.
710 #[must_use]
711 pub fn as_byte(self) -> Option<u8> {
712 match self {
713 Self::Dash => Some(b'-'),
714 Self::Asterisk => Some(b'*'),
715 Self::Underscore | Self::Underscore70 => Some(b'_'),
716 Self::Preserve => None,
717 }
718 }
719}
720
721/// GFM table canonicalisation policy.
722#[derive(Copy, Clone, Debug, PartialEq, Eq)]
723pub enum TableStyle {
724 /// Trim source cell padding and emit one conventional space on
725 /// each side of each cell.
726 Compact,
727 /// Pad cells and delimiter rows to display-width-aligned columns.
728 Align,
729 /// Keep source table spacing.
730 Preserve,
731}
732
733/// Line-ending policy. `Keep` adopts the first newline in the source.
734#[derive(Copy, Clone, Debug, PartialEq, Eq)]
735pub enum EndOfLine {
736 Lf,
737 Crlf,
738 Keep,
739}