mdansi 0.1.0

A blazing-fast Markdown-to-ANSI terminal renderer with built-in syntax highlighting
Documentation

mdANSI

English | 中文

CI Crates.io docs.rs License Rust GitHub Stars Last Commit Issues Platform

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 .toml theme 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)

cargo binstall mdansi

From crates.io

cargo install mdansi

From Source

git clone https://github.com/justinhuangcode/mdANSI.git
cd mdANSI
cargo install --path .

Verify Installation

mdansi --version

Quick Start

# 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

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 mdansi::render_markdown;

let md = "# Hello\n\nThis is **bold** and *italic*.";
let ansi = render_markdown(md);
print!("{}", ansi);

Custom Options

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);

Streaming (LLM 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();

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
[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 = true
mdansi --theme-file my-theme.toml README.md

Color formats: named (red, cyan, bright_blue), hex (#ff5733), 256-palette index (42).


How It Works

  1. Parse -- Markdown input is parsed into an AST via comrak (CommonMark + GFM extensions).
  2. Walk -- The AST is traversed depth-first, converting each node into styled ANSI text segments.
  3. Highlight -- Fenced code blocks are syntax-highlighted via syntect with the active theme.
  4. Layout -- Tables are measured and laid out with Unicode-aware column widths and box-drawing borders.
  5. Wrap -- Long lines are wrapped at word boundaries, respecting ANSI escape sequences and CJK character widths.
  6. Emit -- The final ANSI string is written to stdout (or returned as a String in 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:

cargo bench

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
cargo build          # Build
cargo test           # Run all tests (68 tests)
cargo clippy         # Lint
cargo fmt --check    # Format check
cargo bench          # Benchmarks

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.