mdANSI
English | 中文
A blazing-fast Markdown-to-ANSI terminal renderer with built-in syntax highlighting, streaming mode for LLM output, and TOML-configurable themes. Single binary, zero runtime dependencies.
Why mdANSI?
| Criteria | mdANSI | Markdansi | mdcat | glow |
|---|---|---|---|---|
| Language | Rust | TypeScript | Rust | Go |
| Binary size | ~4MB | N/A (Node.js) | ~10MB | ~8MB |
| Runtime deps | None | Node.js 18+ | None | None |
| Syntax highlighting | Built-in (syntect) | External (Shiki) | Built-in (syntect) | Built-in (glamour) |
| Streaming mode | Yes | Yes | No | No |
| Custom themes | TOML files | Code-only | No | Glamour JSON |
| GFM tables | Yes | Yes | Yes | Yes |
| Footnotes | Yes | No | Yes | No |
| Task lists | Yes | Yes | Yes | Yes |
| Math | LaTeX passthrough | No | No | No |
| OSC-8 hyperlinks | Yes | Yes | Yes | No |
| Line numbers | Yes | No | No | No |
| Text wrapping | Unicode + CJK | Basic | Basic | Yes |
| Startup time | ~1ms | ~100ms | ~5ms | ~3ms |
mdANSI combines the speed of a compiled Rust binary with the flexibility of a full theme system and the streaming capability that LLM-powered workflows demand.
Features
- Built-in syntax highlighting -- 200+ languages via syntect, zero configuration needed
- Full GFM support -- tables, task lists, strikethrough, autolinks, footnotes, math (LaTeX passthrough)
- Streaming mode -- incremental rendering for piped LLM/AI output with buffered multi-line constructs
- TOML theme system -- 4 built-in themes + fully customizable
.tomltheme files - Smart text wrapping -- Unicode-aware, CJK/emoji-correct, orphan prevention
- OSC-8 hyperlinks -- clickable terminal links in supported emulators
- Box-drawn code blocks -- with language labels and optional line numbers
- Adaptive terminal detection -- auto-detects color level, width, and capabilities
- Single static binary -- ~4MB, no runtime dependencies, instant startup
- Dual-mode crate -- use as a CLI tool or embed as a Rust library
Installation
Pre-built Binaries (Recommended)
From crates.io
From Source
Verify Installation
Quick Start
# Render a Markdown file
# Pipe from stdin
|
# Stream mode for LLM output
|
# Custom theme
# With line numbers
Commands
CLI Options
| Flag | Description | Default |
|---|---|---|
[FILE] |
Markdown file to render (stdin if omitted) | -- |
-w, --width <N> |
Terminal width override | auto-detected |
-t, --theme <NAME> |
Color theme name | default |
--theme-file <PATH> |
Custom .toml theme file |
-- |
--no-wrap |
Disable text wrapping | off |
--no-highlight |
Disable syntax highlighting | off |
-n, --line-numbers |
Show line numbers in code blocks | off |
--no-code-wrap |
Disable wrapping inside code blocks | off |
--table-border <S> |
Table borders: unicode / ascii / none |
unicode |
--no-truncate |
Disable table cell truncation | off |
--color <MODE> |
Force color: always / never / auto |
auto |
-s, --stream |
Streaming mode for incremental input | off |
--plain |
Strip all ANSI codes (plain text output) | off |
--list-themes |
List built-in themes | -- |
-h, --help |
Print help | -- |
-V, --version |
Print version | -- |
Environment Variables
| Variable | Description |
|---|---|
MDANSI_WIDTH |
Override terminal width |
MDANSI_THEME |
Default theme name |
NO_COLOR |
Disable all colors (no-color.org) |
FORCE_COLOR |
Force color level: 0-3 |
Library Usage
Basic Rendering
use render_markdown;
let md = "# Hello\n\nThis is **bold** and *italic*.";
let ansi = render_markdown;
print!;
Custom Options
use ;
use theme;
let caps = detect;
let theme = dracula_theme;
let options = RenderOptions ;
let renderer = new;
let output = renderer.render;
print!;
Streaming (LLM Output)
use ;
use io;
let stdout = stdout.lock;
let mut stream = new;
// Feed chunks as they arrive from the LLM
stream.push.unwrap;
stream.push.unwrap;
stream.push.unwrap;
stream.push.unwrap;
// Flush remaining buffer when stream ends
stream.flush_remaining.unwrap;
Themes
Built-in Themes
| Theme | Description |
|---|---|
default |
Balanced colors for dark terminals |
solarized |
Solarized Dark palette |
dracula |
Dracula color scheme |
monochrome |
Bold/italic/dim only, no colors |
Custom TOML Theme
Create a .toml file with any combination of style overrides:
# my-theme.toml
[]
= "#e06c75"
= true
[]
= "#98c379"
= true
[]
= "#61afef"
[]
= "#5c6370"
= true
[]
= "#c678dd"
= true
Color formats: named (red, cyan, bright_blue), hex (#ff5733), 256-palette index (42).
How It Works
- Parse -- Markdown input is parsed into an AST via comrak (CommonMark + GFM extensions).
- Walk -- The AST is traversed depth-first, converting each node into styled ANSI text segments.
- Highlight -- Fenced code blocks are syntax-highlighted via syntect with the active theme.
- Layout -- Tables are measured and laid out with Unicode-aware column widths and box-drawing borders.
- Wrap -- Long lines are wrapped at word boundaries, respecting ANSI escape sequences and CJK character widths.
- Emit -- The final ANSI string is written to stdout (or returned as a
Stringin library mode).
In streaming mode, steps 1-6 run incrementally: single-line content is emitted immediately, while multi-line constructs (code blocks, tables) are buffered until complete.
Architecture
┌──────────────┐
Markdown ───> │ parser.rs │ comrak AST
└──────┬───────┘
│
┌──────▼───────┐
│ render.rs │ AST -> ANSI
└──┬───┬───┬──┘
│ │ │
┌──────────┘ │ └──────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ highlight │ │ table.rs │ │ wrap.rs │
│ .rs │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘
syntect box-drawing Unicode-aware
200+ langs column layout word wrapping
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ style.rs │ │ theme.rs │ │ hyperlink.rs │
│ ANSI codes │ │ TOML themes │ │ OSC-8 links │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐
│ stream.rs │ │ terminal.rs │
│ LLM stream │ │ capability │
│ renderer │ │ detection │
└──────────────┘ └──────────────┘
Project Structure
mdANSI/
├── src/
│ ├── lib.rs # Public API and re-exports
│ ├── main.rs # CLI binary entry point
│ ├── cli.rs # clap argument definitions
│ ├── parser.rs # comrak Markdown parsing wrapper
│ ├── render.rs # Core ANSI rendering engine
│ ├── stream.rs # Streaming renderer (LLM-friendly)
│ ├── style.rs # ANSI style/color primitives
│ ├── theme.rs # Theme system with TOML support
│ ├── table.rs # GFM table layout engine
│ ├── highlight.rs # syntect syntax highlighting
│ ├── wrap.rs # Unicode-aware text wrapping
│ ├── hyperlink.rs # OSC-8 terminal hyperlinks
│ ├── terminal.rs # Terminal capability detection
│ └── error.rs # Error types
├── themes/
│ ├── default.toml # Default dark theme
│ ├── dracula.toml # Dracula theme
│ └── solarized.toml # Solarized Dark theme
├── tests/
│ ├── integration.rs # Integration test suite
│ └── fixtures/ # Test fixtures
├── benches/
│ └── render.rs # Criterion benchmarks
├── .github/
│ └── workflows/
│ └── ci.yml # CI: check, test, clippy, fmt, MSRV, audit
├── Cargo.toml
├── CHANGELOG.md
├── LICENSE-MIT
├── LICENSE-APACHE
└── README.md
Benchmarks
Run benchmarks locally:
Typical results on Apple M-series hardware:
| Benchmark | Time |
|---|---|
| Full document + syntax highlighting | ~2ms |
| Full document, no highlighting | ~0.3ms |
| Plain text output | ~0.2ms |
Security & Environment
| Concern | Mitigation |
|---|---|
| Untrusted Markdown input | comrak sandboxes all parsing; no script execution |
| Stream buffer exhaustion | 10 MB hard limit with automatic flush |
| Theme file loading | TOML deserialization only; no code execution |
| Terminal escape injection | All user content is escaped through the ANSI style layer |
NO_COLOR compliance |
Fully supported per no-color.org |
Troubleshooting
Common issues and solutions are tracked in GitHub Issues.
| Problem | Solution |
|---|---|
| No colors in output | Check NO_COLOR env var; use --color always to force |
| Table columns too narrow | Use --width to set wider terminal width, or --no-truncate |
| Code block not highlighted | Ensure language is specified after the opening fence (e.g., ```rust) |
| Streaming output garbled | Verify your terminal supports ANSI escape sequences |
Contributing
Contributions are welcome! Please open an issue first for significant changes.
# Development workflow
See CHANGELOG.md for release history.
License
Licensed under either of Apache License 2.0 or MIT License at your option.
mdANSI is maintained by Justin Huang.