Skip to main content

Module markdown

Module markdown 

Source
Expand description

Markdown mode: read a CommonMark file as a tree of heading sections. Read-only — Markdown prose is never written back.

A heading owns everything under it until the next heading of its own level or shallower, so the document becomes a tree. Each section reports its heading text and level, its own blocks in source order, the paragraph subset of those, and its child sections under h2/h3/… Anything before the first heading is preamble, which carries the same blocks/paragraph pair.

preamble.blocks.0   → { type: "frontmatter", format: "toml", text: "" }
preamble.paragraph.0 → { type: "paragraph", text: "CI" }       (a badge line)
h1.0.text           → "Real"
h1.0.paragraph.0    → { type: "paragraph", text: "The lead." }
h1.0.blocks.1       → { type: "code", language: "bash", text: "…" }
h1.0.h2.0.text      → "A quick look"

Segments are matched by content as well as by index (see Format::array_rule): h2.look finds the section headed “A quick look”, and matching several sections is an error rather than a guess. That is what makes an address survive editing — h2.3 moves the moment a section is inserted above it, and a top-level index moves the moment a badge line appears above the title.

Sections are the reason the tree is not flat. A heading level is the one piece of hierarchy CommonMark states outright, and folding on it costs nothing while giving every address a stable frame: content is addressed relative to the heading it lives under, not to the top of the file.

What this backend owns is the specification layer — block identification, block boundaries, and the heading nesting that follows from levels — and nothing above it. Where a document’s title lives, whether the first paragraph is a synopsis, which blockquote carries a prompt: those are one project’s layout conventions, and they stay with the caller that holds them. The distinction matters because block boundaries are precisely the part a hand-rolled line scanner gets wrong: setext headings, the seven HTML-block start conditions, four-space indented code, and the rule that an ATX heading interrupts a paragraph are each a rule a ^# regex does not have.

Detection is never automatic: a .md path resolves to no format on its own and the caller must ask for --input-format markdown. The same file is legitimately readable as yaml-frontmatter, and a reader that guesses between two valid readings of one file is the shape-guessing AFDATA avoids.

Deliberately out of scope, so that what this does return is exact:

  • No dialects. No GFM tables, footnotes, task lists, or strikethrough. A table’s rows parse as a paragraph, which is the specification’s answer, not a defect. (A leading +++/--- metadata block is recognised, as its own block kind — see [options] for why that is not a dialect.)
  • No parsing or copying of frontmatter fields. It is reported as one block with format: "toml"|"yaml" and empty text; --input-format toml-frontmatter is the reading that turns it into values. Omitting the raw metadata also prevents this structural reading from becoming a way around field-name-based secret redaction.
  • No recursion into a block’s children. A blockquote or list reports flattened text; its inner block structure is not exposed. (Heading sections are nested — that hierarchy comes from the level, not from walking inside a block.)
  • No byte offsets or columns. Every block does carry 1-based inclusive source_start_line / source_end_line. Every section carries its whole source range plus heading_end_line for the heading alone. These are enough to splice whole Markdown blocks without making every consumer implement UTF-8 byte indexing.
  • No Markdown-to-Markdown transformation. Rewriting a document in place needs source-preserving serialization, which is a separate design, not a rider on a reader.

Functions§

load
Parse content as CommonMark into the section tree described above.