Hongdown
Hongdown is a Markdown formatter that enforces Hong Minhee's Markdown style conventions. The formatter is implemented in Rust using the Comrak library for parsing. It produces consistently formatted Markdown output following a distinctive style used across multiple projects including Fedify, LogTape, and Optique.
Installation
npm
mise
Cargo
Nix
Pre-built binaries
Pre-built binaries for Linux, macOS, and Windows are available on the GitHub Releases page.
Usage
Basic usage
# Format a file and print to stdout
# Format a file in place
# Format multiple files
# Format all Markdown files in a directory (recursive)
# Check if files are formatted (exit 1 if not)
# Show diff of formatting changes
# Read from stdin (use --stdin flag or - as filename)
|
|
# Custom line width
HTML comment directives
Hongdown supports special HTML comment directives to control formatting behavior within documents.
Disable formatting
You can disable formatting for specific sections:
This entire file will not be formatted.
This line preserves its spacing.
The next line will be formatted normally.
Everything here is preserved as-is
until the next heading (h1 or h2).
Next heading
This section will be formatted normally.
This section is not formatted.
This section is formatted again.
Sentence case customization
When sentence case is enabled, you can define document-specific proper nouns and common nouns:
This heading will be formatted as: "Using Swift and Go for python development"
<!-- hongdown-proper-nouns: Word1, Word2 -->– Defines proper nouns to preserve (case-sensitive)<!-- hongdown-common-nouns: Word1, Word2 -->– Overrides built-in proper nouns by treating them as common nouns
These directives are merged with configuration file settings.
Configuration file
Hongdown looks for a .hongdown.toml file in the current directory and
parent directories. You can also specify a configuration file explicitly
with the --config option.
Below is an example configuration with all available options and their default values:
# File patterns (glob syntax)
= [] # Files to format (default: none, specify on CLI)
= [] # Files to skip (default: none)
# Formatting options
= 80 # Maximum line width (default: 80)
[]
= true # Use === underline for h1 (default: true)
= true # Use --- underline for h2 (default: true)
= false # Convert headings to sentence case (default: false)
= [] # Additional proper nouns to preserve (default: [])
= [] # Exclude built-in proper nouns (default: [])
[]
= "-" # "-", "*", or "+" (default: "-")
= 1 # Spaces before marker (default: 1)
= 2 # Spaces after marker (default: 2)
= 4 # Indentation for nested items (default: 4)
[]
= "." # "." or ")" at odd nesting levels (default: ".")
= ")" # "." or ")" at even nesting levels (default: ")")
= "start" # "start" or "end" for number alignment (default: "start")
= 4 # Indentation for nested items (default: 4)
[]
= "~" # "~" or "`" (default: "~")
= 4 # Minimum fence length (default: 4)
= true # Space between fence and language (default: true)
= "" # Default language for code blocks (default: "")
# External code formatters (see "External code formatters" section)
[]
# javascript = ["deno", "fmt", "--ext=js", "-"]
[]
= "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
= 3 # Leading spaces (0-3, default: 3)
[]
= true # "text" to "text" (default: true)
= true # 'text' to 'text' (default: true)
= false # it's to it's (default: false)
= true # ... to ... (default: true)
= false # Disabled by default (use "--" to enable)
= "--" # -- to --- (default: "--", use false to disable)
When include patterns are configured, you can run Hongdown without
specifying files:
# Format all files matching include patterns
# Check all files matching include patterns
CLI options override configuration file settings:
# Use config file but override line width
# Use specific config file
Style rules
Hongdown enforces the following conventions:
Headings
- Level 1 and 2 use Setext-style (underlined with
=or-) - Level 3+ use ATX-style (
###,####, etc.) - Optional sentence case conversion (disabled by default)
Document Title
==============
Section
Sentence case (optional)
When sentence_case = true is set in the configuration, Hongdown automatically
converts headings to sentence case using intelligent heuristics:
The converter:
- Capitalizes only the first word
- Preserves code spans (text in backticks)
- Preserves acronyms (2+ consecutive uppercase letters, e.g.,
API,HTTP) - Preserves proper nouns (built-in list + user-configured)
- Handles hyphenated words (e.g.,
JSON-RPC) - Respects quoted text capitalization
- Preserves non-Latin scripts (CJK, etc.)
You can add custom proper nouns to preserve:
[]
= true
= ["MyCompany", "MyProduct", "MyAPI"]
You can also exclude built-in proper nouns by treating them as common nouns. This is useful for words like “Go” which can be either a programming language or a common verb:
[]
= true
= ["Go", "Swift"] # Treat these as common nouns, not proper nouns
Built-in proper nouns include ~450 entries: programming languages (JavaScript, TypeScript, Python, Rust, Go), technologies (GitHub, Docker, React, Node.js), databases (PostgreSQL, MySQL, MongoDB), countries (United States, Republic of Korea), natural languages (English, Korean, Japanese), and more.
You can also use HTML comment directives to define document-specific proper nouns and common nouns. See the “HTML comment directives” section for details.
Lists
- Unordered lists use
-(space-hyphen-two spaces) - Ordered lists use
1.format - 4-space indentation for nested items
- - -
Code blocks
- Fenced with four tildes (
~~~~) - Language identifier on the opening fence
~~~~ rust
fn main() {
println!("Hello, world!");
}
~~~~
External code formatters
You can configure external formatters for code blocks in your .hongdown.toml. This allows automatic formatting of embedded code using language-specific tools.
[]
# Simple format: command as array
= ["deno", "fmt", "--ext=js", "-"]
= ["deno", "fmt", "--ext=ts", "-"]
= ["rustfmt"]
# With custom timeout (default is 5 seconds)
[]
= ["black", "-"]
= 10
Behavior:
- Language matching is exact only (
javascriptmatchesjavascript, notjs) - Code is passed to the formatter via stdin, formatted output read from stdout
- If the formatter fails (non-zero exit, timeout, etc.), the original code is preserved and a warning is emitted
- External formatters are only available in CLI mode (not in WASM)
To skip formatting for a specific code block, add hongdown-no-format after the
language identifier:
~~~~ python hongdown-no-format
def hello(): print("Hello, World!")
~~~~
The hongdown-no-format keyword is preserved in the output, ensuring the code
block remains unformatted on subsequent runs.
For WASM builds, use the formatWithCodeFormatter function with a callback:
import { formatWithCodeFormatter } from "@hongdown/wasm";
import * as prettier from "prettier";
const { output, warnings } = await formatWithCodeFormatter(markdown, {
codeFormatter: (language, code) => {
if (language === "javascript" || language === "typescript") {
return prettier.format(code, { parser: "babel" });
}
return null; // Keep original for other languages
},
});
Line wrapping
- Lines wrap at approximately 80 display columns
- East Asian wide characters are counted as 2 columns
- Long words that cannot be broken are preserved
Links
- External URLs are converted to reference-style links
- References are placed at the end of each section
- Relative/local URLs remain inline
See the [documentation] for more details.
Tables
- Pipes are aligned accounting for East Asian wide characters
- Minimum column width is maintained
See STYLE.md for the complete style specification, including the philosophy behind these conventions and detailed formatting rules.
Editor integrations
Zed
Add the following to your Zed settings to use Hongdown as the Markdown formatter (contributed by Lee Dogeon):
Neovim
If you use none-ls.nvim (a community-maintained fork of null-ls.nvim), you can register Hongdown as a formatter (contributed by Vladimir Rubin):
local null_ls = require
local hongdown =
null_ls.
Library usage
Rust
Hongdown can also be used as a Rust library:
use ;
let input = "# Hello World\nThis is a paragraph.";
let options = default;
let output = format.unwrap;
println!;
JavaScript/TypeScript
Hongdown is available as a WebAssembly-based library for JavaScript and TypeScript:
import { format, formatWithWarnings } from "@hongdown/wasm";
// Basic usage
const markdown = "# Hello\nWorld";
const formatted = await format(markdown);
// With options
const result = await format(markdown, {
lineWidth: 100,
setextH1: false,
fenceChar: "`",
});
// Get warnings along with formatted output
const { output, warnings } = await formatWithWarnings(markdown);
if (warnings.length > 0) {
for (const warning of warnings) {
console.warn(`Line ${warning.line}: ${warning.message}`);
}
}
The library works in Node.js, Bun, Deno, and web browsers. See the TypeScript type definitions for all available options.
Development
This project uses mise for task management.
Initial setup
After cloning the repository, set up the Git pre-commit hook to automatically run quality checks before each commit:
Quality checks
The following tasks are available:
# Run all quality checks
# Individual checks
See AGENTS.md for detailed development guidelines including TDD practices, code style conventions, and commit message guidelines.
Etymology
The name Hongdown is a portmanteau of Hong (from Hong Minhee, the author) and Markdown. It also sounds like the Korean word hongdapda (홍답다), meaning “befitting of Hong” or “Hong-like.”
License
Distributed under the GPL-3.0-or-later. See LICENSE for more information.