Skip to content

feat: support pandoc fancy_lists (I. / A. / a. / i. / (1)) behind an opt-in preference - #5754

Open
kingdomseed wants to merge 24 commits into
marktext:developfrom
kingdomseed:feat/academic-outline-muya
Open

kingdomseed wants to merge 24 commits into
marktext:developfrom
kingdomseed:feat/academic-outline-muya

Conversation

@kingdomseed

@kingdomseed kingdomseed commented Oct 15, 2026 •

Copy link
Copy Markdown

Closes #4379

Summary

Adds pandoc's fancy_lists to the editor, behind a new preference that is off by default (Preferences → Markdown → Letter and Roman numeral lists). As discussed in #4379, this replaces the custom outline block this PR first proposed. There is no new syntax: ordered lists read, edit, and save pandoc's markers.

Style Markers
Decimal 1. 1) (1)
Auto-numbered #. #) (#)
Letters a. A. a) (a)
Roman i. I. i) (i)

With the preference off, the editor behaves exactly as it does on develop:

With the preference off, I. and A. lines stay paragraphs

With it on, the same markers become real nested lists:

A seven-level outline rendered as nested fancy lists

The file stays plain pandoc markdown. Pandoc 3.12.1 reads this source as the same seven nested lists:

The same document in source mode

Outlines

A list that starts with I. is an outline. Tab gives the new sublist the next style, I → A → 1 → a → i → (1) → (a), and Shift+Tab moves back. Every marker after the change renumbers. Lists that start with 1. keep today's Tab behavior.

Tab, Shift+Tab and Enter in an outline

Typing I. , A. , a. , i. , (1) , #. , or 1) at the start of a paragraph starts a list. Pandoc's two-space rule keeps prose such as B. Russell as text. Fancy lists are ordinary ordered lists, so there are no new menu entries or shortcuts.

Markers follow the theme:

The outline in the dark theme

What else changes

  • Editor. 1) and (1) lists show their real delimiters. Today 1) renders as 1..
  • HTML and PDF export, and copy. Lists export as <ol type="I"> and similar, so other apps and screen readers see real lists.
  • Pandoc export. With the preference on, Export → Pandoc reads the file as pandoc markdown. Pandoc's gfm and commonmark readers do not nest these lists, even with +fancy_lists.
  • Pandoc reader fix. The markdown reader that backslash math already uses turned off syntax that gfm reads natively: fenced code, intraword _, backslash line breaks, and shortcut links. Fancy-list exports use that reader, so I fixed it in 038ac51f. The fix also corrects exports for people who use backslash math.

Code

The parser, model, and serializer live in packages/muya. One table of marker styles in utils/listMarker.ts drives parsing, formatting, spacing, and the outline ladder.

packages/muya/src/
├── utils/listMarker.ts                       # styles, parse, format, two-space rule, outline ladder
├── utils/marked/extensions/fancyLists.ts     # block tokenizer, registered only when the option is on
├── utils/marked/extensions/fancyListsHtml.ts # <ol type> renderer for export and copy
├── state/                                    # meta.style, pandoc-spaced serializer
├── block/content/paragraphContent/           # Tab, Shift+Tab, Enter
├── block/base/format.ts                      # typing a marker starts a list
└── assets/styles/                            # @counter-style rules for ) and ()
packages/desktop/src/
├── main/utils/pandoc.ts                      # pandoc reader for fancy lists
└── renderer/…/prefComponents/markdown/       # the preference

Decimal lists still go through marked's own tokenizer, so documents without fancy markers parse the same with the preference on or off.

Type of change

  • Bug fix (non-breaking, fixes an issue)
  • New feature (non-breaking, adds functionality)
  • Breaking change (causes existing functionality to change)
  • Documentation update

Test plan

  • New tests added

    • Unit tests in muya cover parsing, serializing, Tab, Enter, typing, copy, and export. Every parse and round-trip fixture is also checked against pandoc 3.12.1 when pandoc is installed.
    • Desktop unit tests cover the preference and the pandoc reader.
    • test/e2e/fancy-lists.spec.ts covers rendering, Tab and Shift+Tab, Enter, and the off switch.
  • Ran locally on macOS:

    • lint and typecheck
    • muya test (2845), test:spec (1347), lint:types, build, and check-circular
    • desktop test (1117)
    • the new e2e spec (4 of 4)
    • validate-licenses

    pandoc-discovery.spec.ts fails locally only because pandoc is installed on that machine. The same 2 cases fail on develop.

  • Manually tested on: macOS

Notes for reviewers [optional]

  • Pandoc rules this follows:
    • Sublists start at 1, a, or i.
    • A change of marker style starts a new list.
    • Single letters only. Past z, letters wrap to a, as pandoc writes them.
    • Two spaces after A. and after single-letter Roman numerals such as I. and V..
    • Two adjacent lists of the same style get a <!-- --> between them, pandoc's way to end a list.
  • With the preference off, saved files and Tab behavior match develop byte for byte. Tests assert both.
  • endListItemsAtFancyMarkers swaps marked's internal nextBulletRegex, so check it on marked upgrades.
  • Known edge case: Export → Pandoc reads a letter or Roman marker directly under a paragraph line (para then (a) x, no blank line) as a list, while the editor keeps it as text, as pandoc's own markdown does. The export reader enables lists_without_preceding_blankline so para then 1. x exports as a list, matching how the editor treats decimal lists.
  • Follow-ups not in this PR:
    • Turndown writes pasted HTML lists as decimal.
    • Undo of a style-only change does not redraw the marker. This already applies to start.

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Add utils/listMarker.ts, which reads and writes pandoc fancy_lists
markers (decimal, alpha and roman styles with ., ) and () delimiters)
and gives the spacing pandoc requires after each marker. Ordered list
state gains an optional style and a typed delimiter. The fancyLists
option is off by default and re-parses the document when changed.
With fancyLists on, a block tokenizer reads ordered lists that open with
an alpha or roman marker, or with a (1) style marker, following pandoc:
two spaces after a capital letter or initial-like numeral with a
period, later items read in the first item's style, a new list on any
change of style or delimiter, nested lists starting at one, and no
interruption of a paragraph. Decimal and bullet items also end at a
fancy marker. Ordered list state keeps the style, delimiter and start.
Ordered list items are written in their list's style and delimiter,
with two spaces after markers such as I. and A. that pandoc would
otherwise read as initials. The wider marker moves continuation and
nested lines to the new content column. Adjacent lists are told apart
by style as well as delimiter, and two ordered lists in a row with the
same markers are separated by an HTML comment so they read back as two
lists. The tests check each fixture against pandoc when it is
installed.
The markdown serializer takes a fancyLists option, passed from the
editor options. With it off, two adjacent ordered lists with the same
markers are written as before.
Ordered lists carry their style and delimiter as data attributes, and
the stylesheet maps them to counter styles, so a. IV) and (1) lists
show their own markers instead of decimal ones. A Chromium e2e test
reads the marker text from the accessibility tree.
Tab makes a sublist that numbers from one. Under an upper-roman list
it takes the outline marker for its depth (I., A., 1., a., i., (1),
(a)); other lists keep the parent marker. Outdent and split compare
the list style as well as the delimiter, so items of a different style
are not merged into the same list. Turning an ordered task list into
an ordered list from the front menu keeps its style.
With fancyLists on, a paragraph whose first line starts with a marker
such as I.  A.  a. i. (1) (a) or c) becomes an ordered list in that
style. Capital letters and initial-like numerals with a period need two
spaces, so B. Russell stays text, and a list inside a list item has to
start at one. Converting an item to a task item keeps the list style.
With fancyLists off, Tab copies the parent list's start number into
the new sublist as before.
With fancyLists on, HTML export, static rendering and clipboard HTML read
pandoc fancy lists and write each ordered list as <ol type> plus a class
naming its style and delimiter. Export CSS draws the ) and (1) markers with
@counter-style and keeps nested typed lists from being forced to decimal.
The markdown reader used for backslash math switched off every extension
the gfm listing does not name. gfm reads fenced code, intraword
underscores, backslash line breaks, shortcut reference links and start
numbers without listing them, so those exports turned code blocks into
inline code and lost list start numbers.
Preferences > Markdown gets a switch for pandoc's fancy_lists extension,
off by default. Toggling it re-parses the open document so lettered and
Roman numeral lists appear or turn back into text.
With fancyLists on, Convert with Pandoc reads the document with pandoc's
markdown reader, shaped like gfm, plus fancy_lists. pandoc's gfm reader
takes +fancy_lists but folds an indented A. item into its parent's text,
so an outline only nests when markdown reads it.
With fancyLists on, the / quick-insert menu and Paragraph > Outline
(Cmd+Option+K on macOS, Ctrl+Alt+K on Linux) insert an ordered list
in upper-roman style, turn the list at the cursor into one, or wrap a
multi-block selection into one. Windows gets no default shortcut because
Ctrl+Alt types characters there. The entry is hidden while the option is off.
Copied markdown now ends a list with <!-- --> before another list in the
same style, as saving does, so pasting it reads back two lists.
The block menu builds its Turn Into list from the quick-insert entries
through canTurnInto, so a paragraph already offers Outline with its icon
while fancyLists is on. Pin that, and that choosing it writes I.  text.
Launch the app with fancyLists on and check that an outline renders as
nested typed lists, that Tab and Shift+Tab move an item along the outline
ladder, that Enter continues roman numbering, and that the markers stay
paragraph text with the preference off.
Add the fancyLists preference to the preferences reference, and a Fancy
lists section to the syntax guide covering the markers, the two-space rule,
the outline levels Tab moves through, and the <!-- --> list separator.
@kingdomseed
kingdomseed force-pushed the feat/academic-outline-muya branch from 3956d85 to 8d7979b Compare October 17, 2026 18:07
@kingdomseed kingdomseed changed the title feat: academic outlines (I. / A. / 1. / a. / i.) behind an opt-in preference feat: support pandoc fancy_lists (I. / A. / a. / i. / (1)) behind an opt-in preference Oct 17, 2026
@Jocs

Jocs commented Oct 20, 2026

Copy link
Copy Markdown
Member

@kingdomseed I haven't reviewed the code thoroughly yet, but I've tested the new feature on macOS.

  1. My understanding is that fancy lists are an extension of ordered lists, rather than a completely separate list type. Based on this, I don't think we need to add new entries to the paragraph menu or Quick Insert, or introduce any new keyboard shortcuts.
  2. I also tested #. , which should be supported as part of the Pandoc fancy_lists syntax, but it doesn't seem to work.

A fancy list is an ordered list with another marker style, so it needs
no list type of its own. Remove the / quick-insert entry, Turn Into >
Outline, Paragraph > Outline, its keyboard shortcuts, locale strings,
icons and tests. Typing I. and two spaces still starts an outline, and
Tab still steps through the outline levels.
With fancyLists on, #., #) and (#) open an ordered list in pandoc's
default style. The editor and HTML export number it in digits, and a
save keeps the # markers, also after an edit. Typing #. and a space
starts one. As in pandoc, a # item continues a list of any style, a #
list continues with digits, and adjacent lists that would read back as
one are ended with <!-- -->.
@kingdomseed

Copy link
Copy Markdown
Author

Thanks for testing! Both are addressed in the latest commits:

  1. I removed the Paragraph menu entry, the Quick Insert entry and the shortcut, so fancy lists are just ordered lists now. You start one by typing its marker (e.g. I. plus two spaces), and inside an I. list Tab picks the next level.
  2. #., #) and (#) now work like pandoc: auto-numbered, shown as 1, 2, 3, and the # markers are kept when saving.

One question: right now there's no way to change an existing ordered list's style in the editor. Would you be OK with a "List style" choice on ordered lists, shown only when fancy lists are on? It would sit in the ¶ menu next to Turn Into, offer 1. / a. / A. / i. / I. / (1) / #., and restyle the whole list in place. It's not a new list type, just a style on the existing ordered list.

@kingdomseed
kingdomseed force-pushed the feat/academic-outline-muya branch from 86eaef6 to aab6136 Compare October 21, 2026 15:33
@Jocs

Jocs commented Oct 25, 2026

Copy link
Copy Markdown
Member

Sorry for the delay in reviewing this PR. I've been busy with some refactoring recently, including the Selection refactor and support for editing transactions. Could you please rebase onto the latest develop branch and update the code as needed to align with these changes?

I'll review it as soon as I can. Thanks again for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] (Academic) Outline Support (Markdown - Not Outliner)

2 participants