Formatting

This section provides instructions and examples for using Panache to format your documents, showing examples of the formatting rules and how to customize them.

Panache’s formatting rules are designed to produce clean, consistent Markdown output that adheres to common style guidelines while preserving the semantic meaning of the original document. We often try to stick quite close to the output of Pandoc’s writer, but not always. Panache is an opinionated formatter and provides a minimal set of options to configure the formatting behavior.

The golden rule is that a markdown document should be readable in its raw form, which for instance has guided the decision to convert --- horizontal rules to a full line of hyphens.

Another important principle is that Panache’s formatter is idempotent: running the formatter multiple times on the same document should not produce different output after the first run. To make this work, Panache therefore needs to escape certain characters that might otherwise cause semantic drift on subsequent runs1

Text Wrapping

Paragraphs

Panache supports three modes of text wrapping for paragraphs, which are controlled via the configuration file:

[format]
wrap = "reflow" # or "preserve" or "sentence"

If wrap is set to reflow, Panache will reformat paragraphs to fit within the number of columns specified by the line-width configuration option (default 80). If wrap is set to preserve, Panache will keep existing line breaks and not reflow paragraphs. If wrap is set to sentence, Panache will break lines at sentence boundaries, ensuring that each sentence starts on a new line.

In sentence mode, Panache applies conservative no-break rules for common abbreviations (for example i.e. and e.g.), so these do not trigger sentence splits by themselves.

When document metadata provides lang (as in Quarto and Pandoc frontmatter), Panache uses it to choose sentence-boundary behavior. Currently, English rules are applied as the default fallback when language-specific rules are not available.

Input
First sentence with a [link text](https://example.com) and inline math $a + b = c$. Second sentence; third sentence!
Output ("reflow")
First sentence with a [link text](https://example.com) and inline math
$a + b = c$. Second sentence; third sentence!
Output ("sentence")
First sentence with a [link text](https://example.com) and inline math $a + b = c$.
Second sentence; third sentence!

Hard Line Breaks

Hard line breaks, which are created either by ending a line with two or more spaces or by using a backslash (\n), are preserved regardless of the wrapping mode:

Input (with two spaces at the end of the first line)
First line  
Second line
Output
First line\
Second line

Headings

ATX Headings

Headings are normalized with consistent spacing. Trailing # characters in ATX headings are removed. Spacing between the # characters and the heading text is normalized to a single space. Headings are separated from surrounding content by a blank line.

Input
#Heading 1
Text

##  Heading 2

###   Heading 3
Output
# Heading 1

Text

## Heading 2

### Heading 3

Heading Attributes

Attributes are preserved and formatted to have a single space between the class names and no space between the class names and the opening { character.

Input
## Heading with classes{.important  .highlight }
Output
## Heading with classes {.important .highlight}

Setext Headings

Setext-style headings are converted to ATX style.

Input
Heading 1
=========

Heading 2
---------
Output
# Heading 1

## Heading 2

Emphasis and Strong

Basic Formatting

Emphasis markers are consistently normalized to asterisks (*) for italics and double asterisks (**) for bold text. Underscores are converted to asterisks. Ambiguous cases are resolved by escaping emphasis markers to avoid idempotency issues.

Input
Single asterisks: *italic text*

Single underscores: _italic text_

Double asterisks: **bold text**

Double underscores: __bold text__

**foo*
Output
Single asterisks: *italic text*

Single underscores: *italic text*

Double asterisks: **bold text**

Double underscores: **bold text**

\*\*foo\*

Code

Inline Code

For inline code, Panache normalizes spacing within the code span and ensures that the code is enclosed in the appropriate number of backticks to avoid conflicts with the content. If the code contains spaces, it will be wrapped in double backticks to preserve the spaces.

Input
Code with spaces: ` code with spaces `

`` ```r ``
Output
Code with spaces: `code with spaces`

```` ```r ````

Tab Stops

Tabs in regular text are always normalized to spaces. To preserve tabs in literal code spans and code blocks, set:

[format]
tab-stops = "preserve"
tab-width = 4

Fenced Code Blocks

Fenced code blocks are formatted with consistent fencing and attributes. We normalize them to the shortcut style from Pandoc.

Input
```haskell
qsort [] = []
```

``` {.haskell}
qsort [] = []
```

```haskell {.numberLines}
qsort [] = []
```

``` {.haskell .numberLines}
qsort [] = []
```
Output
```haskell
qsort [] = []
```

```haskell
qsort [] = []
```

```haskell {.numberLines}
qsort [] = []
```

```haskell {.numberLines}
qsort [] = []
```

Indented Code Blocks

Indented code blocks are normalized to fenced code blocks for consistency:

Input
    a <- 1
    b <- a^2
Output
```
a <- 1
b <- a^2
```

Lists

Unordered Lists

Bullet list markers are standardized to -. Indentation is normalized to two spaces per level. Loose lists are converted to tight lights if they only contain plain (non-block/paragraph) items.

Input
* Item 1
  + Nested item
      *  Deeply nested

 +  Item 2
Output:
- Item 1
  - Nested item
    - Deeply nested
- Item 2

Ordered Lists

Ordered lists are handled similarly to unordered lists.

Task Lists

GitHub-style task lists use the standardized - marker, and indent to the start of the text.

Input
+ [ ] Parent task
   *   [ ] Nested unchecked task
   - [x]   Nested checked task
- [X] Another parent task
Output
- [ ] Parent task
       - [ ] Nested unchecked task
       - [x] Nested checked task
- [x] Another parent task

Definition Lists

Definition lists are indented with three spaces after the marker, flush the marker to the left, and indent following lines to four spaces.

Panache also normalizes compact vs loose definition items by structure, making them

  • compact when each definition is a single plain paragraph-like block, and
  • loose when a definition contains multiple blocks or starts with a non-paragraph block.
Input
Term 1

: Definition 1

Term 2
:   Definition 2a
 : Definition 2b

Term 3
:   - List
    with lazy continuation
    - > a
      > b
      > c
Output
Term 1

:   Definition 1

Term 2
:   Definition 2a
:   Definition 2b

Term 3

:   - List with lazy continuation

    - > a b c

Fancy Lists

Fancy lists are indented to line up with the first character of the list item text.

Input
(i) Parens style
(ii) Second item
(iii) Third item

iv. Starting at four
v. Five
vi. Six
vii. Seven
viii. Eight
ix. Nine
x. Ten
Output
  (i) Parens style
 (ii) Second item
(iii) Third item

  iv. Starting at four
   v. Five
  vi. Six
 vii. Seven
viii. Eight
  ix. Nine
   x. Ten

Block Quotes

Block quotes are formatted with consistent > markers:

Input
>This is a block quote. This
>paragraph has two lines.
>
> 1. This is a list inside a block quote.
> 2. Second item.

> This is a block quote. This
paragraph has two lines.
Output
> This is a block quote. This paragraph has two lines.
>
> 1. This is a list inside a block quote.
> 2. Second item.

> This is a block quote. This paragraph has two lines.

Tables

Panache supports all Pandoc table types and normalizes alignment and spacing while preserving content and attributes. Formatted table blocks are indented by two spaces.

Pipe Tables

Pipe tables are normalized to have consistent spacing and alignment. The header separator row is standardized to use hyphens and colons for alignment, and spacing within cells is normalized.

Input
| Right | Left | Default | Center|
|------:|:-----|---------|:------:|
| 12  |  12  |    12   |    12  |
|  123  |  123 |   123   |   123  |
|  1  |    1 |     1   |     1 |
Output
  | Right | Left | Default | Center |
  | ----: | :--- | ------- | :----: |
  |    12 | 12   | 12      |   12   |
  |   123 | 123  | 123     |  123   |
  |     1 | 1    | 1       |   1    |

Grid Tables

Grid tables are normalized to have consistent spacing and alignment.

Input
+--------+----------------+
| Var    | Description    |
+========+================+
| `A`    | Example value. |
+--------+----------------+
Output
  +-----+----------------+
  | Var | Description    |
  +=====+================+
  | `A` | Example value. |
  +-----+----------------+

Simple Tables

We normalize simple tables so that cell contents align with the alignment specified in the header row, and spacing within cells is normalized.

Input
   Right     Left     Center     Default
 -------     ------ ----------   ---------------
      12       12      12            12
   123       123       123          123
       1     1        1             1

   :  A caption
Output
    Right     Left     Center     Default
  -------     ------ ----------   -------
       12     12         12       12
      123     123       123       123
        1     1          1        1

  Table: A caption

Multiline Tables

Input
-------------------------------------------------------------
 Centered   Default           Right Left
  Header    Aligned         Aligned Aligned
----------- ------- --------------- -------------------------
  First     row              12.0   Example of a row that
                                    spans multiple lines.


  Second    row                 5.0 Here's another one. Note
                                    the blank line between
                                    rows.
-------------------------------------------------------------
Output
  -------------------------------------------------------------
   Centered   Default           Right Left
    Header    Aligned         Aligned Aligned
  ----------- ------- --------------- -------------------------
     First    row                12.0 Example of a row that
                                      spans multiple lines.

    Second    row                 5.0 Here's another one. Note
                                      the blank line between
                                      rows.
  -------------------------------------------------------------

Table Captions

Table captions are normalized to be on a separate line from the table, prefixed with Table:, and flush left with the table content.

Captions also follow the configured wrapping mode (reflow, preserve, or sentence). In reflow mode, long captions wrap to the configured line-width. In sentence mode, each sentence starts on its own line.

Input
 a   b
--- ---
 1   2

   : A caption with extra spaces
Output
   a   b
  --- ---
   1   2

  Table: A caption with extra spaces
Output (wrap = "sentence")
   a   b
  --- ---
   1   2

  Table: First caption sentence.
  Second caption sentence.

Math

Inline Math

Inline math is (currently) untouched.

Display Math

Display math is formatted on its own lines with leading indentation depending on the math-indent configuration option (default 0):

Input
$$E = mc^2$$
Output
$$
E = mc^2
$$

Fenced Divs

Fenced divs are normalized so that there is no blank line between the opening fence and the content and no space between the closing fence and the content. Attributes are preserved and normalized. With nested divs, the inner div

Input
::: Warning ::::::
This is a warning.

::: Danger
This is a warning within a warning.
:::
::::::::::::::::::
Output
::: Warning
This is a warning.

::::: Danger
This is a warning within a warning.
:::::
:::

Footnotes

Reference Footnotes

Input
[^longnote]: Here's one with multiple blocks.

    Subsequent paragraphs are indented to show that they
belong to the previous footnote.

        { some.code }

    The whole paragraph can be indented, or just the first
    line.  In this way, multi-paragraph footnotes work like
    multi-paragraph list items.
Output
[^longnote]: Here's one with multiple blocks.

    Subsequent paragraphs are indented to show that they belong to the previous
    footnote.

    ```
    { some.code }
    ```

    The whole paragraph can be indented, or just the first line. In this way,
    multi-paragraph footnotes work like multi-paragraph list items.

Horizontal Rulers

Horizontal rulers are normalized to hyphens and extend to line-width.

Input
---
***
___
Output (line-width = 60):
-----------------------------------------------------------

-----------------------------------------------------------

-----------------------------------------------------------

Blank Lines

Collapsing

By default, multiple blank lines are collapsed to one:

Input
Paragraph 1


Paragraph 2
Output
Paragraph 1

Paragraph 2

If you want to preserve all existing blank lines, set:

[format]
blank-lines = "preserve"

Frontmatter

YAML

YAML frontmatter is parsed and normalized by pretty_yaml.

Input
---
echo:    false
list:
-  a
-     b
---
Text
Output
---
echo: false
list:
  - a
  - b
---

Text

Chunk Options

For executable code blocks, Panache convert options specified in the header (e.g. ```{r, echo=FALSE}) to the hashpipe comment style.

Input
```{r foobar, echo=FALSE, dependson = c("foo", "bar"), fig.cap = "A caption"}
a <- 1
b <- 2
```
Output
```{r, dependson=c("foo", "bar")}
#| label: foobar
#| echo: false
#| fig-cap: "A caption"
a <- 1
b <- 2
```

Complex structures, like the dependson option in the example above, are preserved as-is without attempting to convert them. Formatting of the options is handled by pretty_yaml—as in the case of YAML frontmatter. If you want to ensure that your caption is wrapped, don’t use a quoted scalar value for the caption. Instead use a block scalar, via >-.

Ignore Directives

You can selectively disable formatting for specific regions using HTML comment directives:

Ignore Formatting Only

Use panache-ignore-format-start and panache-ignore-format-end to preserve specific formatting:

Normal paragraph will be wrapped and formatted.

<!-- panache-ignore-format-start -->
This    paragraph   has    custom     spacing
that  will   be   preserved   exactly.
<!-- panache-ignore-format-end -->

Back to normal formatting.

This is useful for:

  • ASCII art or diagrams
  • Tables with specific spacing
  • Pre-formatted text blocks
  • Code examples that aren’t in code blocks

Ignore Both Formatting and Linting

Use panache-ignore-start and panache-ignore-end to disable both formatting and linting:

<!-- panache-ignore-start -->
#### Heading with unusual spacing
Custom    formatting   and   linting   rules   ignored
<!-- panache-ignore-end -->

Footnotes

  1. This is precisely what Pandoc’s writer also does.↩︎