A streaming markdown printer for the terminal that renders GitHub Flavored Markdown with ANSI escape codes. The key feature is incremental emission: blocks are emitted immediately once parsed, not waiting for the entire document.
Warning: I wrote this code as an experiment in LLM development. I do not speak fluent rust and I have not read the markdown parser. I'm pretty sure it cannot do anything dangerous, but you've been warned.
- Reasonably attractive, colorful display
- Parses some HTML
- Renders links as OSC8 terminal hyperlinks
- This sounds fancy, but just means you can click on links if your terminal supports it
brew install llimllib/tap/mdrivercargo install mdriverDownload the latest release for your platform from the GitHub Releases page:
- Linux:
mdriver-x86_64-unknown-linux-gnu.tar.gz - macOS:
mdriver-x86_64-apple-darwin.tar.gz(Intel) ormdriver-aarch64-apple-darwin.tar.gz(Apple Silicon)
Extract and add to your PATH:
tar xzf mdriver-*.tar.gz
sudo mv mdriver /usr/local/bin/git clone https://github.com/llimllib/mdriver.git
cd mdriver
cargo build --releaseThe binary will be available at target/release/mdriver.
# Read from file
mdriver README.md
# Pipe markdown from a file
cat document.md | mdriver
# Pipe from echo
echo "# Hello World" | mdriver
# Redirect from file
mdriver < document.md
# Use a specific syntax highlighting theme
mdriver --theme "InspiredGitHub" README.md
# Set default theme via environment variable
MDRIVER_THEME="Solarized (dark)" mdriver README.md
# List available themes
mdriver --list-themes
# Render images using kitty graphics protocol
mdriver --images kitty document.md
# Control color output (auto, always, never)
mdriver --color=always README.md | less -R
# Show help
mdriver --help$ echo "# Demo\n\nThis is **bold** and *italic*." | mdriverOutput (with ANSI colors in your terminal):
- # Demo (in blue and bold)
- This is bold and italic
- ✅ Streaming: Renders markdown incrementally as it arrives
- ✅ ATX Headings:
# Headingwith blue/bold formatting - ✅ Paragraphs: Text blocks with inline formatting
- ✅ Code Blocks: Fenced blocks with
```and syntax highlighting - ✅ Lists: Unordered (
-) and ordered (1.) lists - ✅ Inline Formatting:
**bold**,*italic*,`code`with nested support - ✅ Hyperlinks:
[text](url)converted to clickable OSC8 terminal links - ✅ Image Rendering:
with kitty graphics protocol support - ✅ Syntax Highlighting: 100+ languages supported with customizable themes
- ✅ ANSI Colors: Beautiful terminal output with 24-bit true color
- ✅ Color Control: Auto-detect, always, or never modes for flexible output
- ✅ Zero Warnings: Strict clippy linting, no compiler warnings
mdriver uses the syntect library for syntax highlighting, supporting 100+ languages with customizable color themes.
Use mdriver --list-themes to see all available themes. Popular options include:
- InspiredGitHub - Bright, vibrant colors inspired by GitHub's syntax highlighting
- Solarized (dark) - The classic Solarized dark color scheme
- Solarized (light) - Solarized optimized for light backgrounds
- base16-ocean.dark - Calm oceanic colors (default)
- base16-mocha.dark - Warm mocha tones
- base16-eighties.dark - Retro 80s aesthetic
There are three ways to configure the theme (in order of precedence):
- Command-line flag:
mdriver --theme "InspiredGitHub" file.md - Environment variable:
export MDRIVER_THEME="Solarized (dark)" - Default:
base16-ocean.dark
# Use InspiredGitHub theme
mdriver --theme "InspiredGitHub" README.md
# Set environment variable for persistent default
export MDRIVER_THEME="Solarized (dark)"
mdriver README.md
# Combine with piping
MDRIVER_THEME="base16-mocha.dark" cat file.md | mdrivermdriver can render images inline in your terminal using the kitty graphics protocol. This feature works with any terminal that supports the kitty graphics protocol (kitty, WezTerm, Ghostty, etc.).
Use the --images kitty flag to enable image display:
# Render local images
mdriver --images kitty document.md
# Works with remote URLs
echo "" | mdriver --images kitty
# Combine with theme selection
mdriver --theme "InspiredGitHub" --images kitty README.md- Auto-resize: Images automatically resize to fit terminal width while preserving aspect ratio
- Remote URLs: Fetches and displays images from HTTP/HTTPS URLs
- Graceful fallback: Shows alt text when image fails to load
- Backward compatible: Without
--imagesflag, images render as plain text - Extensible: Architecture supports future protocols (sixel, iTerm2, etc.)
# My Document
Here's a screenshot:

And a remote image:
# Render with images
mdriver --images kitty document.mdNote: Image rendering requires a terminal that supports the kitty graphics protocol. In terminals without support, images will display as alt text.
By default, mdriver automatically detects whether to use ANSI colors based on whether stdout is a terminal. You can override this behavior with the --color flag.
| Mode | Description |
|---|---|
auto |
Use colors only when stdout is a terminal (default) |
always |
Always emit ANSI color codes, even when piping |
never |
Never use colors; pass markdown through unchanged |
# Default behavior (auto-detect)
mdriver README.md # Colors in terminal
mdriver README.md | less # No colors (not a tty)
# Force colors when piping to a pager
mdriver --color=always README.md | less -R
# Disable colors entirely
mdriver --color=never README.md
# Alternative syntax (space instead of =)
mdriver --color always README.mdPiping to a pager with colors:
mdriver --color=always README.md | less -RThe -R flag tells less to interpret ANSI escape sequences.
Saving colored output to a file:
mdriver --color=always README.md > output.txtThe file will contain ANSI codes that display colors when cated to a terminal.
Plain text output:
mdriver --color=never README.md > plain.mdPasses the markdown through without any formatting.
mdriver decodes HTML entities in markdown text, supporting both named entities and numeric character references.
| Entity | Character | Description |
|---|---|---|
| Essential (XML) | ||
& |
& |
Ampersand |
< |
< |
Less than |
> |
> |
Greater than |
" |
" |
Quotation mark |
' |
' |
Apostrophe |
| Whitespace | ||
|
Non-breaking space | |
| Typographic | ||
– |
– |
En dash |
— |
— |
Em dash |
… |
… |
Horizontal ellipsis |
‘ |
' |
Left single quote |
’ |
' |
Right single quote |
“ |
" |
Left double quote |
” |
" |
Right double quote |
• |
• |
Bullet |
· |
· |
Middle dot |
| Symbols | ||
© |
© |
Copyright |
® |
® |
Registered |
™ |
™ |
Trademark |
° |
° |
Degree |
± |
± |
Plus-minus |
× |
× |
Multiplication |
÷ |
÷ |
Division |
| Fractions | ||
¼ |
¼ |
One quarter |
½ |
½ |
One half |
¾ |
¾ |
Three quarters |
| Currency | ||
¢ |
¢ |
Cent |
£ |
£ |
Pound |
€ |
€ |
Euro |
¥ |
¥ |
Yen |
| Arrows | ||
← |
← |
Left arrow |
→ |
→ |
Right arrow |
↑ |
↑ |
Up arrow |
↓ |
↓ |
Down arrow |
In addition to named entities, mdriver supports numeric references for any Unicode character:
- Decimal:
©→© - Hexadecimal:
©→©
$ echo "5 < 10 — Tom & Jerry © 2024" | mdriver
5 < 10 — Tom & Jerry © 2024This project uses a comprehensive conformance test suite to verify streaming behavior, markdown parsing, and ANSI formatting.
Tests are written as TOML fixture files that specify:
- Input chunks: Markdown arriving incrementally (simulating streaming)
- Expected emissions: What should be output after each chunk (empty string if block incomplete)
- Raw ANSI codes: Actual escape sequences for exact terminal output matching
name = "heading-basic"
description = "Heading should emit after newline is received"
[[chunks]]
input = "#"
emit = ""
[[chunks]]
input = " Hello"
emit = ""
[[chunks]]
input = "\n"
emit = "\u001b[1;34m# Hello\u001b[0m\n"Key Points:
- Each
[[chunks]]represents a piece of markdown fed to the parser input: The markdown chunkemit: Expected terminal output (empty""means no emission yet)- ANSI codes use
\u001bformat (TOML Unicode escape)
Tests are organized in tests/fixtures/:
blocks/- Individual block types (headings, paragraphs, code blocks, lists)streaming/- Incremental emission and block boundary detectionansi/- ANSI escape sequence formatting (bold, italic, colors)complex/- Real-world documents with mixed block types
# Run all conformance tests
cargo test
# Run specific test category
cargo test test_block_fixtures
cargo test test_streaming_fixtures
cargo test test_ansi_fixtures
cargo test test_complex_fixtures
# Run with verbose output
cargo test -- --nocaptureWhen tests fail, you see clear diagnostics:
Running 4 tests from blocks...
✗ heading-basic
Heading should emit after newline is received
Chunk 4 failed:
Input: "\n"
Expected: "\u{1b}[1;34m# Hello\u{1b}[0m\n"
Actual: ""
- Create a
.tomlfile in the appropriatetests/fixtures/subdirectory - Define test name and description
- Add chunks with input and expected emissions
- Use
\u001bfor ESC character in ANSI codes
Example ANSI codes:
- Bold:
\u001b[1m...\u001b[0m - Italic:
\u001b[3m...\u001b[0m - Color:
\u001b[1;34m...\u001b[0m(bold blue) - Background:
\u001b[48;5;235m...\u001b[0m
All Systems Operational ✅
- ✅ Parser Implementation: Complete with full streaming support
- ✅ Test Suite: 8 conformance tests - all passing
- ✅ CLI: Working binary for command-line usage
- ✅ Code Quality: Zero compiler warnings, zero clippy errors
- ✅ Documentation: Comprehensive CLAUDE.md for AI assistants
Test Coverage:
- Block types: heading, paragraph, code block, list
- Streaming: incremental emission, block boundaries
- Formatting: inline ANSI codes (bold, italic, code)
- Complex: mixed document scenarios
Potential areas for expansion:
- Additional GFM features (tables, task lists)
- Additional image protocols (sixel, iTerm2)
- Terminal width awareness and text wrapping
- Performance benchmarks for large documents
mdriver/
├── Cargo.toml
├── README.md
├── CLAUDE.md # AI assistant context and guidelines
├── gfmspec.md # GitHub Flavored Markdown specification
├── .clippy.toml # Clippy linting configuration
├── src/
│ ├── lib.rs # StreamingParser implementation
│ └── main.rs # CLI binary
└── tests/
├── conformance.rs # Test runner
├── common/
│ ├── mod.rs
│ └── fixture_loader.rs
└── fixtures/
├── blocks/ # 4 tests: heading, paragraph, code_block, list
├── streaming/ # 2 tests: incremental_emit, block_boundaries
├── ansi/ # 1 test: inline_formatting
└── complex/ # 1 test: mixed_document
- Streaming First: Blocks emit immediately when complete, enabling real-time rendering
- Test-Driven: Comprehensive test suite defines expected behavior before implementation
- Exact Output: Tests verify exact ANSI codes, not just content
- Incremental Testing: Tests verify streaming property, not just final output
- Zero Tolerance: No compiler warnings, no clippy errors - strict code quality standards
This project maintains strict code quality requirements:
# All must pass before committing:
cargo fmt # Format code
cargo build # No warnings
cargo build --release # No warnings
cargo clippy --all-targets --all-features -- -D warnings # No errors
cargo test # All tests passSee CLAUDE.md for comprehensive development guidelines and best practices.
- Fork the repository
- Create a feature branch
- Write tests first (TDD approach)
- Implement feature to pass tests
- Ensure all quality checks pass
- Submit pull request
CLAUDE.md: Comprehensive guide for AI assistants and developersgfmspec.md: GitHub Flavored Markdown specification (authoritative source).clippy.toml: Linting configurationtests/fixtures/: Conformance test cases in TOML format
MIT