English | δΈζ
A blazing-fast Markdown-to-ANSI CLI for terminal rendering, LLM streaming, and syntax highlighting. π
| 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.
- 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
cargo binstall mdansicargo install mdansigit clone https://github.com/justinhuangcode/mdANSI.git
cd mdANSI
cargo install --path .mdansi --version# Render a Markdown file
mdansi README.md
# Pipe from stdin
cat CHANGELOG.md | mdansi
# Stream mode for LLM output
llm_command | mdansi --stream
# Custom theme
mdansi --theme dracula doc.md
# With line numbers
mdansi -n README.md| 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 | -- |
| 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 |
use mdansi::render_markdown;
let md = "# Hello\n\nThis is **bold** and *italic*.";
let ansi = render_markdown(md);
print!("{}", ansi);use mdansi::{Renderer, RenderOptions, Theme, TerminalCaps};
use mdansi::theme;
let caps = TerminalCaps::detect();
let theme = theme::dracula_theme();
let options = RenderOptions {
width: 100,
line_numbers: true,
..RenderOptions::from_terminal(&caps)
};
let renderer = Renderer::new(theme, options);
let output = renderer.render("## Hello from mdANSI!");
print!("{}", output);use mdansi::{StreamRenderer, RenderOptions, Theme};
use std::io;
let stdout = io::stdout().lock();
let mut stream = StreamRenderer::new(stdout, Theme::default(), RenderOptions::default());
// Feed chunks as they arrive from the LLM
stream.push("# Streaming\n").unwrap();
stream.push("This is ").unwrap();
stream.push("**incremental** ").unwrap();
stream.push("output.\n").unwrap();
// Flush remaining buffer when stream ends
stream.flush_remaining().unwrap();| Theme | Description |
|---|---|
default |
Balanced colors for dark terminals |
solarized |
Solarized Dark palette |
dracula |
Dracula color scheme |
monochrome |
Bold/italic/dim only, no colors |
Create a .toml file with any combination of style overrides:
# my-theme.toml
[heading1]
fg = "#e06c75"
bold = true
[heading2]
fg = "#98c379"
bold = true
[inline_code]
fg = "#61afef"
[code_border]
fg = "#5c6370"
dim = true
[link_text]
fg = "#c678dd"
underline = truemdansi --theme-file my-theme.toml README.mdColor formats: named (red, cyan, bright_blue), hex (#ff5733), 256-palette index (42).
- 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.
ββββββββββββββββ
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 β
ββββββββββββββββ ββββββββββββββββ
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
Run benchmarks locally:
cargo benchTypical results on Apple M-series hardware:
| Benchmark | Time |
|---|---|
| Full document + syntax highlighting | ~2ms |
| Full document, no highlighting | ~0.3ms |
| Plain text output | ~0.2ms |
| 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 |
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 |
Contributions are welcome! Please open an issue first for significant changes.
# Development workflow
cargo build # Build
cargo test # Run all tests (68 tests)
cargo clippy # Lint
cargo fmt --check # Format check
cargo bench # BenchmarksSee CHANGELOG.md for release history.
Licensed under either of Apache License 2.0 or MIT License at your option.
mdANSI is maintained by Justin Huang.