markdown

    Incremental Markdown parser and compiler

    markdown
    parser
    cst
    incremental
    gfm
    Download zip
    Author
    Version
    0.8.3
    License
    MIT
    Last updated
    16 days ago
    Downloads
    16K

    #@mizchi/markdown

    CST-based incremental Markdown parser for JavaScript/MoonBit.

    A cross-platform (JS/WASM/native) Markdown compiler optimized for real-time editing with incremental parsing.

    #Features

    • Fast: Edit-position based incremental updates inspired by CRDTs Go Brrr
    • CommonMark compliant: passes all 652 examples of the CommonMark 0.31.2 spec
    • Source-oriented CST: Retains node spans so byte-preserving tools can edit the original source
    • Incremental parsing: Re-parses only changed blocks (up to 42x faster)
    • GFM: GitHub Flavored Markdown support (tables, task lists, strikethrough)
    • Cross-platform: Works on JS, WASM-GC, and native targets
    • HTML rendering: Built-in HTML renderer with remark-html compatible output
    • mdast compatible: AST follows mdast specification


    #JavaScript API

    npm install @mizchi/markdown

    #Usage

    import { parse, toHtml, toMarkdown } from "@mizchi/markdown"; // Parse to AST const ast = parse("# Hello\n\n**Bold** text"); console.log(ast.children[0].type); // "heading" // Convert to HTML const html = toHtml("See https://example.com/docs\n"); // => '<p>See <a href="https://example.com/docs">https://example.com/docs</a></p>\n' // Disable bare URL links when you need plain text output const plain = toHtml("See https://example.com/docs\n", { autolink: false }); // => "<p>See https://example.com/docs</p>\n" // Match CommonMark 0.31.2 rendering by disabling GFM renderer extensions const commonmark = toHtml("<script>raw</script>\n\nhttps://example.com\n", { autolink: false, tagfilter: false, }); // => "<script>raw</script>\n<p>https://example.com</p>\n" // Normalize markdown const normalized = toMarkdown("# Hello\n\n\n\nWorld"); // => "# Hello\n\nWorld\n"

    #WebAssembly API

    The same one-shot API is available from a Wasm GC build using JS String Builtins. JavaScript strings cross the Wasm boundary directly, without a UTF-8 linear-memory copy. parse() also builds ordinary JavaScript objects through externref imports, avoiding an AST JSON stringify/parse round trip. The module initializes its Wasm instance once and then exposes synchronous functions:

    import { parse, toHtml, toMarkdown } from "@mizchi/markdown/wasm"; const ast = parse("# Hello"); const html = toHtml("See https://example.com/docs\n"); const markdown = toMarkdown("# Hello\n\n\nWorld");

    This subpath requires an ESM runtime with Wasm GC, JS String Builtins, and top-level await support. The handle-based incremental API remains available from the default @mizchi/markdown entry point.

    #Cloudflare Workers

    The default @mizchi/markdown entry point cannot currently run inside a Cloudflare Worker. It imports the SIMD module through WebAssembly ESM Integration named exports, while Wrangler exposes imported .wasm files as a default-exported WebAssembly.Module. Supporting workerd therefore requires a workerd-specific bridge that instantiates that module, selected through a workerd conditional export. See Cloudflare's documentation for Wasm module bundling and conditional exports.

    The repository's current playground deployment is unaffected because its Worker only serves prebuilt static assets; it does not import the parser into the workerd runtime.

    WikiLinks are disabled by default to keep CommonMark-compatible behavior. Pass { wikilinks: true } to parse [[target]] and [[target|label]].

    import { parse, toHtml } from "@mizchi/markdown"; const ast = parse("[[MoonBit#syntax|MoonBit syntax]]", { wikilinks: true }); // ast.children[0].children[0].type === "wikiLink" const html = toHtml("[[MoonBit|MoonBit notes]]", { wikilinks: true }); // => '<p><a href="MoonBit">MoonBit notes</a></p>\n'

    #Extended syntax

    The parser also exposes semantic nodes for GitHub alerts, footnotes, display math, container/text directives, definition lists, and block attributes:

    > [!WARNING] > Check this first.[^details] $$ E = mc^2 $$ :::note Optional title Markdown stays available inside the container. ::: Use :badge[stable]{.green level=high}. Term : Definition # Attributed heading {#intro .wide} [^details]: Footnote content.

    Display math is represented as a math block with a raw value. The default HTML output is an escaped <pre> fallback, so JavaScript consumers can instead send the AST value to KaTeX or another math renderer.

    #Incremental Parsing

    For real-time editing scenarios:

    import { createDocument, insertEdit } from "@mizchi/markdown"; // Create document handle const doc = createDocument("# Hello"); // Access AST, HTML, or Markdown console.log(doc.ast); // Parsed AST console.log(doc.toHtml()); // "<h1>Hello</h1>\n" console.log(doc.toMarkdown()); // "# Hello\n" // Incremental update (faster than full re-parse) const edit = insertEdit(7, 6); // Insert 6 chars at position 7 const newDoc = doc.update("# Hello World", edit); // Free resources when done doc.dispose(); newDoc.dispose();

    #TypeScript Support

    Full TypeScript definitions are included:

    import { parse, Document, Block, Inline } from "@mizchi/markdown"; const ast: Document = parse("# Hello"); const heading = ast.children[0] as HeadingBlock; console.log(heading.level); // 1


    #MoonBit API

    #Installation

    moon add mizchi/markdown

    #Usage

    // Parse markdown
    let result = @markdown.parse("# Hello\n\nWorld")
    let doc = result.document

    // Serialize back to canonical, normalized Markdown
    let output = @markdown.serialize(doc)

    // Render to HTML
    let html = @markdown.render_html(doc)

    // Or use convenience function
    let html = @markdown.md_to_html("# Hello\n\nWorld")
    let linked = @markdown.md_to_html("See https://example.com/docs\n")
    let plain = @markdown.md_to_html("See https://example.com/docs\n", autolink=false)

    // Disable the GFM tagfilter when matching plain CommonMark raw-HTML output
    let commonmark_html = @markdown.md_to_html("<script>raw</script>\n", tagfilter=false)

    // Enable the WikiLink extension explicitly
    let wiki_html = @markdown.md_to_html("[[MoonBit|MoonBit notes]]", wikilinks=true)

    #Display-math renderer

    The MoonBit HTML renderer has an explicit display-math boundary. The callback receives the raw block contents and returns trusted HTML, making KaTeX or a different backend replaceable without coupling it to the parser:

    let document = @markdown.parse("$$\nx^2\n$$\n").document
    let options = @markdown.RenderOptions::default().with_math_block_renderer(
    fn(source) { render_with_katex(source) },
    )
    let html = @markdown.render_html_with_options(document, options)

    Without a callback, math is safely HTML-escaped in <pre class="math math-display"><code>…</code></pre>.

    #Native CLI

    Build the native command with just build-native. The executable is produced from src/cmd/mmmd-native and can be installed or renamed as mmmd:

    _build/native/release/build/cmd/mmmd-native/mmmd-native.exe --format html < document.md _build/native/release/build/cmd/mmmd-native/mmmd-native.exe --format tui < document.md

    The native TUI format emits normalized Markdown. Mermaid remains an ordinary fenced code block; diagram rendering is available in the JavaScript/Wasm CLI.

    #Incremental Parsing

    // Initial parse
    let result = @markdown.parse(source)
    let doc = result.document

    // Create edit info
    let edit = @markdown.EditInfo::replace(
    change_start, // Start position
    old_length, // Length of replaced text
    new_length // Length of new text
    )

    // Incremental update (reuses unchanged blocks)
    let inc_result = @markdown.parse_incremental(doc, old_source, new_source, edit)
    let new_doc = inc_result.document

    #Typed MDX Declarations

    mizchi/markdown/x/mdx extracts MDX JSX components and validates their attributes against a closed, typed schema. It is intended for document metadata and domain declarations, rather than evaluating arbitrary MDX code.

    Expressions use a deterministic literal-only subset: strings, booleans, and arrays of strings. For example, requires={["auth.mfa"]} is valid, while requires={loadRequirements()} is rejected.

    import {
    "mizchi/markdown",
    "mizchi/markdown/x/mdx" @mdx,
    }

    let schema = @mdx.MdxSchema::closed([
    @mdx.ComponentSchema::new(
    "Fold",
    [
    @mdx.PropSchema::required("id", @mdx.MdxValueType::Text),
    @mdx.PropSchema::required(
    "kind",
    @mdx.MdxValueType::OneOf(["concept", "procedure"]),
    ),
    @mdx.PropSchema::optional("requires", @mdx.MdxValueType::TextList),
    ],
    ),
    ])

    let source = #|<Fold id="auth.mfa" kind="procedure" requires={["auth.login"]} />
    let checked = @mdx.type_check_mdx(@markdown.parse(source).document, schema)
    let is_valid = checked.is_valid()

    #Playground

    pnpm install moon build --target js pnpm exec vite

    #Frontend Editor Package

    @mizchi/markdown/editor exports the Luna-based markdown editor without bundling syntax highlighters into the initial module. Code block highlighters are loaded on demand through dynamic imports under @mizchi/markdown/highlight.

    The editor uses @luna_ui/luna as a JSX runtime and signal library; it is declared as an optional peer dependency — install it alongside @mizchi/markdown only if you use the editor entry. See frontend/editor/README.md for the editor-specific docs.

    pnpm add @mizchi/markdown @luna_ui/luna

    import { SyntaxHighlightEditor } from "@mizchi/markdown/editor"; import "@mizchi/markdown/editor/style.css"; <SyntaxHighlightEditor value={() => markdown} onChange={(next) => setMarkdown(next)} />;

    You can also preload or call a language highlighter explicitly:

    import { loadHighlighter } from "@mizchi/markdown/highlight"; const highlightMoonBit = await loadHighlighter("moonbit"); const html = highlightMoonBit?.("fn main { println(\"hi\") }");

    The currently split lazy highlighter entries are typescript, moonbit, json, html, css, bash, and rust.

    #Performance

    DocumentFull ParseIncrementalSpeedup
    10 paragraphs68.89µs7.36µs9.4x
    50 paragraphs327.99µs8.67µs37.8x
    100 paragraphs651.14µs15.25µs42.7x

    #Native CPU profiling

    moon run --profile can sample the same approximately 1 MiB corpus used by the competitor benchmark. On macOS it records an Instruments Time Profiler trace; on Linux Moon uses perf.

    just profile-native parse 100 just profile-native render 200 just profile-native parse-render 100

    parse rebuilds the document each iteration, render repeatedly renders one pre-parsed document, and parse-render measures the public combined path. Moon prints a demangled hot-function summary and writes profile.json plus the full trace below _build/native/release/profile/cmd/profile/.

    The same corpus can be profiled on the JavaScript backend with Node's V8 CPU profiler. The resulting *.cpuprofile file can be opened in Chrome DevTools:

    just profile-js parse 50

    Profiles are written below _build/js/release/profile/cmd/profile/.

    #JavaScript Wasm SIMD via ESM Integration

    On the JavaScript target, ASCII inline-text runs of at least 64 UTF-16 code units use a 396-byte Wasm SIMD scanner. The default npm entry point statically imports js/inline_marker_simd.wasm through WebAssembly ESM Integration; there is no inline byte array or manual WebAssembly.instantiate step. TextEncoder writes directly into reusable Wasm memory, while a non-ASCII prefix falls back to the UTF-16 scalar scanner so source offsets remain exact.

    The WAT source and generated .wasm artifact can be rebuilt and checked separately. just bench-js preloads the same ESM bridge for MoonBit's direct JS benchmark runner:

    just inline-wasm-build just inline-wasm-check just bench-js

    #Documentation

    See docs/markdown.md for detailed architecture and design.

    #CommonMark Compatibility

    md_to_html and the public JavaScript toHtml() API render all 652 examples of the CommonMark 0.31.2 spec byte for byte. Use { autolink: false, tagfilter: false } with toHtml() to select the CommonMark-faithful renderer configuration. scripts/gen-spec-tests.js checks the MoonBit API on every test run, while the JavaScript regression suite locks the 11 examples affected by these two GFM renderer extensions.

    Bare-URL autolinking, tables, strikethrough, task lists and footnotes are GFM extensions on top of CommonMark. Tagfilter is also a GFM extension and escapes raw script, style, and textarea tags when enabled.

    Markdown output (md_to_markdown) is normalized rather than byte-preserving. It uses stable markers and block spacing, and emits parsed link reference definitions in a canonical trailing section. Use node spans and the original source when an editor needs byte-preserving updates.

    #Credits

    • Fonts: PlemolJP (SIL Open Font License 1.1) — bundled in playground/public/fonts/

    #License

    MIT

    AlertKind

    pub(all) enum AlertKind {
    Note
    Tip
    Important
    Warning
    Caution
    } derive(Eq,
    Debug
    )

    GitHub alert kind (> [!NOTE], etc.).
    impl Show for AlertKind

    AlertKind::slug

    fn AlertKind::slug(self : AlertKind) -> String

    Stable lowercase name used by renderers and JSON APIs.

    Block

    pub(all) enum Block {
    ThematicBreak(marker~ : Char, count~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Heading(level~ : Int, style~ : HeadingStyle, children~ : Array[Inline], closing_hashes~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Paragraph(children~ : Array[Inline], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    FencedCode(fence_marker~ : FenceMarker, fence_length~ : Int, info~ : String, code~ : String, indent~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    MathBlock(value~ : String, fence_length~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Directive(name~ : String, meta~ : String, children~ : Array[Block], fence_length~ : Int, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    DefinitionList(items~ : Array[DefinitionItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Attributed(block~ : Block, attributes~ : Array[MarkdownAttribute], span~ : Span)
    IndentedCode(code~ : String, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Blockquote(children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Alert(kind~ : AlertKind, children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    BulletList(marker~ : BulletMarker, tight~ : Bool, items~ : Array[ListItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    OrderedList(start~ : Int, delimiter~ : OrderedDelimiter, tight~ : Bool, items~ : Array[ListItem], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    HtmlBlock(html~ : String, span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    Table(header~ : Array[TableCell], alignments~ : Array[TableAlign], rows~ : Array[Array[TableCell]], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    BlankLines(count~ : Int, span~ : Span)
    FootnoteDefinition(label~ : String, children~ : Array[Block], span~ : Span, leading_trivia~ : Trivia, trailing_trivia~ : Trivia)
    }

    Block-level nodes

    BulletMarker

    pub(all) enum BulletMarker {
    Dash
    Asterisk
    Plus
    } derive(Eq,
    Debug
    )

    List marker for unordered lists: -, *, +

    CodeBlockInfo

    pub(all) struct CodeBlockInfo {
    lang : String
    filename : String
    meta : String
    }

    Parsed info string from code block fence.

    Format: "lang:filename {meta}"

    Examples:
    • "javascript"{ lang: "javascript", filename: "", meta: "" }
    • "js:app.js"{ lang: "js", filename: "app.js", meta: "" }
    • "ts:index.ts {highlight=[1,3]}" { lang: "ts", filename: "index.ts", meta: "{highlight=[1,3]}" }

    DefinitionItem

    pub(all) struct DefinitionItem {
    term : Array[Inline]
    definitions : Array[Array[Inline]]
    }

    One term and one or more inline definitions in a definition list.

    Document

    pub(all) struct Document {
    frontmatter : Frontmatter?
    children : Array[Block]
    definitions : Array[LinkDefinition]
    span : Span
    }

    Document root

    EditInfo

    pub(all) struct EditInfo {
    offset : Int
    old_len : Int
    new_len : Int
    }

    Using #valtype to avoid heap allocation for this small struct

    EditInfo::delete

    fn EditInfo::delete(offset : Int, len : Int) -> EditInfo

    Create an edit info for deletion

    EditInfo::insert

    fn EditInfo::insert(offset : Int, len : Int) -> EditInfo

    Create an edit info for insertion

    EditInfo::replace

    fn EditInfo::replace(offset : Int, old_len : Int, new_len : Int) -> EditInfo

    Create an edit info for replacement

    EmphasisMarker

    pub(all) enum EmphasisMarker {
    Asterisk
    Underscore
    } derive(Eq,
    Debug
    )

    Emphasis marker: * or _

    FenceMarker

    pub(all) enum FenceMarker {
    Backtick
    Tilde
    } derive(Eq,
    Debug
    )

    Fence marker for code blocks: ``` or ~~~
    impl Show for FenceMarker

    Frontmatter

    pub(all) struct Frontmatter {
    raw : String
    entries : Array[(String, String)]
    span : Span
    }

    Frontmatter (YAML)

    HardBreakStyle

    pub(all) enum HardBreakStyle {
    TwoSpaces
    Backslash
    } derive(Eq,
    Debug
    )

    Hard break style

    HeadingStyle

    pub(all) enum HeadingStyle {
    Atx
    Setext
    } derive(Eq,
    Debug
    )

    Heading style

    IncrementalResult

    pub(all) struct IncrementalResult {
    document : Document
    reused_before : Int
    reparsed : Int
    reused_after : Int
    }

    Result of incremental parsing

    Inline

    pub(all) enum Inline {
    Text(content~ : String, span~ : Span)
    SoftBreak(span~ : Span)
    HardBreak(style~ : HardBreakStyle, span~ : Span)
    Emphasis(marker~ : EmphasisMarker, children~ : Array[Inline], span~ : Span)
    Strong(marker~ : EmphasisMarker, children~ : Array[Inline], span~ : Span)
    Strikethrough(children~ : Array[Inline], span~ : Span)
    Code(content~ : String, backtick_count~ : Int, span~ : Span)
    Directive(name~ : String, label~ : String, attributes~ : Array[MarkdownAttribute], span~ : Span)
    WikiLink(target~ : String, label~ : String, fragment~ : String, span~ : Span)
    Link(children~ : Array[Inline], url~ : String, title~ : String, span~ : Span)
    RefLink(children~ : Array[Inline], label~ : String, style~ : ReferenceStyle, span~ : Span)
    Autolink(url~ : String, is_email~ : Bool, span~ : Span)
    Image(alt~ : String, url~ : String, title~ : String, span~ : Span)
    RefImage(alt~ : String, label~ : String, style~ : ReferenceStyle, span~ : Span)
    HtmlInline(html~ : String, span~ : Span)
    FootnoteReference(label~ : String, span~ : Span)
    }

    Inline-level nodes

    LinkDefinition

    pub(all) struct LinkDefinition {
    label : String
    url : String
    title : String
    span : Span
    }

    Link reference definition [label]: url "title"

    ListItem

    pub(all) struct ListItem {
    children : Array[Block]
    checked : Bool?
    marker_offset : Int
    content_offset : Int
    span : Span
    }

    List item

    MarkdownAttribute

    pub(all) struct MarkdownAttribute {
    name : String
    value : String
    } derive(Eq,
    Debug
    )

    One normalized block attribute from {#id .class key=value}.

    OrderedDelimiter

    pub(all) enum OrderedDelimiter {
    Dot
    Paren
    } derive(Eq,
    Debug
    )

    Ordered list delimiter: . or )

    ParseResult

    pub(all) struct ParseResult {
    document : Document
    definitions : Array[LinkDefinition]
    }

    Parse result

    ReferenceStyle

    pub(all) enum ReferenceStyle {
    Full
    Collapsed
    Shortcut
    } derive(Eq,
    Debug
    )

    Syntax used by a link or image reference.

    RenderOptions

    pub struct RenderOptions {
    code_highlighter : (CodeBlockInfo, String) -> String?
    math_block_renderer : (String) -> String?
    }

    Render options with plugin support

    RenderOptions::default

    fn RenderOptions::default() -> RenderOptions

    Create default render options

    RenderOptions::with_highlighter

    fn RenderOptions::with_highlighter(highlighter : (CodeBlockInfo, String) -> String) -> RenderOptions

    Create render options with code highlighter

    RenderOptions::with_math_block_renderer

    fn RenderOptions::with_math_block_renderer(self : RenderOptions, renderer : (String) -> String) -> RenderOptions

    The hook result is treated as trusted HTML; escaping belongs to the hook.

    RenderOptions::with_simple_highlighter

    fn RenderOptions::with_simple_highlighter(highlighter : (String, String) -> String) -> RenderOptions

    Create render options with simple highlighter (lang, code) -> String

    Scanner

    pub(all) struct Scanner {
    source : String
    chars : Array[Char]
    pos : Int
    len : Int
    utf16_offsets : Array[Int]?
    }

    Scanner state - uses UTF-16 indexing directly for BMP input and Array[Char] for non-BMP input. Handles Unicode correctly by tracking UTF-16 offsets for non-BMP characters.

    Scanner::advance

    fn Scanner::advance(self : Scanner, n : Int) -> Unit

    Advance position by n characters

    Scanner::consume

    fn Scanner::consume(self : Scanner) -> Char?

    Consume and return current character (O(1))

    Scanner::consume_str

    fn Scanner::consume_str(self : Scanner, s : String) -> Bool

    Match and consume a string

    Scanner::count_char

    fn Scanner::count_char(self : Scanner, c : Char) -> Int

    Count consecutive occurrences of a character from current position - O(1) per char

    Scanner::count_leading_spaces

    fn Scanner::count_leading_spaces(self : Scanner) -> Int

    Count leading spaces (without advancing) - O(1) per char

    Scanner::is_blank_line

    fn Scanner::is_blank_line(self : Scanner) -> Bool

    Check if current line is blank (only whitespace) - O(1) per char

    Scanner::is_eof

    fn Scanner::is_eof(self : Scanner) -> Bool

    Check if at end of input

    Scanner::matches

    fn Scanner::matches(self : Scanner, s : String) -> Bool

    Match a string at current position - optimized with Array[Char]

    Scanner::new

    fn Scanner::new(source : String) -> Scanner

    Create a new scanner

    Scanner::peek

    fn Scanner::peek(self : Scanner) -> Char?

    Peek current character (O(1) with Array[Char])

    Scanner::peek_at

    fn Scanner::peek_at(self : Scanner, offset : Int) -> Char?

    Peek character at offset from current position (O(1))

    Scanner::read_line

    fn Scanner::read_line(self : Scanner) -> String

    Read until end of line (not consuming newline) - O(1) per char

    Scanner::remaining

    fn Scanner::remaining(self : Scanner) -> String

    Get remaining substring from current position

    Scanner::restore

    fn Scanner::restore(self : Scanner, pos : Int) -> Unit

    Restore to saved position

    Scanner::save

    fn Scanner::save(self : Scanner) -> Int

    Save current position

    Scanner::skip_line

    fn Scanner::skip_line(self : Scanner) -> Unit

    Skip to next line (consuming newline if present) - O(1) per char

    Scanner::skip_spaces

    fn Scanner::skip_spaces(self : Scanner) -> Int

    Skip whitespace (space and tab only) - O(1) per char

    Scanner::substring

    fn Scanner::substring(self : Scanner, start : Int, end : Int) -> String

    Get substring from start to end position (code point indices)

    Span

    pub(all) struct Span {
    from : Int
    to : Int
    } derive(Eq,
    Debug
    )

    Using #valtype to avoid heap allocation for this frequently-used small struct
    impl Show for Span

    Span::empty

    fn Span::empty() -> Span

    Empty span (for synthetic nodes)

    Span::new

    fn Span::new(from : Int, to : Int) -> Span

    Create a span

    TableAlign

    pub(all) enum TableAlign {
    Left
    Center
    Right
    None
    } derive(Eq,
    Debug
    )

    Table alignment
    impl Show for TableAlign

    TableCell

    pub(all) struct TableCell {
    children : Array[Inline]
    span : Span
    }

    Table cell

    Trivia

    pub(all) struct Trivia {
    content : String
    } derive(Eq,
    Debug
    )

    Trivia represents non-semantic characters that should be preserved
    impl Show for Trivia

    Trivia::empty

    fn Trivia::empty() -> Trivia

    Trivia::new

    fn Trivia::new(content : String) -> Trivia

    char_is

    fn char_is(opt : Char?, c : Char) -> Bool

    Check if char option matches specific char

    char_is_digit

    fn char_is_digit(opt : Char?) -> Bool

    Check if char option is a digit

    is_alphanumeric

    fn is_alphanumeric(c : Char) -> Bool

    Check if character is alphanumeric

    is_digit

    fn is_digit(c : Char) -> Bool

    Check if character is a digit

    is_letter

    fn is_letter(c : Char) -> Bool

    Check if character is ASCII letter

    is_punctuation

    fn is_punctuation(c : Char) -> Bool

    Check if character is a punctuation mark

    is_unicode_punctuation

    fn is_unicode_punctuation(c : Char) -> Bool

    A "Unicode punctuation character" as CommonMark 0.31.2 defines it: anything in a punctuation (P*) or symbol (S*) general category.

    The ranges below cover the blocks that actually carry punctuation and symbols; letters, digits and marks are deliberately excluded so that flanking is decided correctly for ordinary text.

    is_unicode_whitespace

    fn is_unicode_whitespace(c : Char) -> Bool

    A "Unicode whitespace character" as CommonMark defines it: anything in the Zs general category, plus tab, line feed, form feed and carriage return.

    is_whitespace

    fn is_whitespace(c : Char) -> Bool

    Check if character is ASCII whitespace

    md_parse_and_render

    fn md_parse_and_render(source : String, strict? : Bool, wikilinks? : Bool) -> String

    Parse markdown source and serialize back to a normalized string. Convenience wrapper around parse + serialize used by compatibility tests.

    md_to_html

    fn md_to_html(source : String, wikilinks? : Bool, autolink? : Bool, tagfilter? : Bool) -> String

    Parse markdown and render to HTML

    md_to_html_literal

    fn md_to_html_literal(source : String, wikilinks? : Bool, positions? : Bool, image_preview? : Bool) -> String

    Parse markdown and render with the literal renderer in one step.

    md_to_html_strict

    fn md_to_html_strict(source : String, wikilinks? : Bool, autolink? : Bool, tagfilter? : Bool) -> String

    Parse markdown and render to HTML (strict mode)

    Kept for backwards compatibility: the parser is now always spec-strict.

    parse

    fn parse(source : String, strict? : Bool, wikilinks? : Bool) -> ParseResult

    Parse markdown source into CST.

    strict is accepted for backwards compatibility; the parser always follows the CommonMark rules now.

    parse_code_block_info

    fn parse_code_block_info(info : String) -> CodeBlockInfo

    Parse code block info string into components

    parse_incremental

    fn parse_incremental(old_doc : Document, old_source : String, new_source : String, edit : EditInfo, wikilinks? : Bool) -> IncrementalResult

    Parse incrementally using edit hint

    parse_inlines

    fn parse_inlines(text : String, strict? : Bool, wikilinks? : Bool) -> Array[Inline]

    Parse inline content from text.

    strict is accepted for backwards compatibility; the parser now always follows the CommonMark algorithm.

    render_html

    fn render_html(doc : Document, autolink? : Bool, tagfilter? : Bool) -> String

    Render a document to HTML

    render_html_literal

    fn render_html_literal(doc : Document, positions? : Bool, image_preview? : Bool) -> String

    Render a document to HTML using the literal (source-preserving) mode.

    • positions: emit data-src-start / data-src-end on top-level block elements (see module docs).
    • image_preview: emit an <img class="md-image-preview"> slot inside each <span class="md-image"> wrapper alongside the ![alt](url) source characters. The <img> carries no visible text (textContent is empty), so the overlay invariant is preserved. If the alt text ends in :wN (for example ![diagram:w500](...)), the preview slot receives width metadata and the real image alt text is diagram. Default display: none ships in @mizchi/markdown/editor/overlay.css; the consumer opts in by adding .with-image-preview to a container above the rendered output (or by overriding the rule themselves).

    render_html_with_options

    fn render_html_with_options(doc : Document, options : RenderOptions, autolink? : Bool, tagfilter? : Bool) -> String

    Render with replaceable block renderers such as KaTeX.

    serialize

    fn serialize(doc : Document) -> String

    Serialize document to markdown string

    serialize_definitions

    fn serialize_definitions(defs : Array[LinkDefinition]) -> String

    Serialize link definitions