A beautiful diagnostics renderer for compiler errors and warnings
Key Features • Installation • Quick Start • API Reference • Testing
📦 For Rust Users: This README covers the complete Musubi project (Lua/C/Rust implementations).
Looking for Rust API documentation? → See the comprehensive Rust API docs with examples and usage guides.
Rust crate:musubi-rs
Overview
Musubi (結び, "connection" in Japanese) is a high-performance diagnostics renderer inspired by Rust's Ariadne library. It produces beautiful, color-coded diagnostic messages with precise source location highlighting, multi-line spans, and intelligent label clustering.
Originally ported from Rust to Lua for rapid prototyping, Musubi has evolved into a production-ready multi-language implementation:
- Pure Lua: Feature-complete reference implementation with 100% test coverage
- C Library: High-performance port with Lua bindings (
musubi.h,musubi.c) - Rust Crate: Safe FFI wrapper with ergonomic builder API
All implementations produce pixel-perfect identical output and share the same test suite (95 tests, 2400+ lines).
Key Features
✨ Beautiful Output
- Multi-line diagnostics with color-coded labels
- Intelligent label clustering and virtual row rendering
- Unicode and CJK character support
- ASCII/Unicode glyph sets for terminal compatibility
🚀 Performance Optimized
- O(n) rendering complexity (vs original O(n²))
- Pre-computed width caching for UTF-8 strings
- Binary search for line windowing calculations
- Zero-copy source file handling with streaming support
🎯 Improved Implementation
- Cleaner Implement with seprated small functions
- Bugfixes towards original Ariadne implement
- New feature: Line limited support
- New feature: No message label rendered
🛡️ Production Ready
- 100% test coverage (all reachable code covered)
- Memory-safe C implementation
- Comprehensive error handling
- Tested on Lua 5.1, 5.4, and LuaJIT
Example
local mu = require
local cg = mu.
print
Output:
Installation
Requirements
Lua Implementation:
- Lua 5.1+ or LuaJIT
lua-utf8library (requiresutf8.widthindexfor line width limiting)- Optional:
luaunitfor running tests,luacovfor coverage analysis
C Implementation:
- C89-compatible compiler (GCC, Clang, MSVC)
- Lua 5.1+ headers for Lua bindings
- Optional:
lcovfor coverage reports
Building
Lua (No build required):
# Install dependencies
# Copy ariadne.lua to your project
C Library with Lua Bindings:
# Compile shared library
# Or with coverage instrumentation
macOS:
Quick Start
Basic Usage (C Bindings)
local mu = require
-- Create a color generator for automatic color cycling
local cg = mu.
-- Build a report
local report = mu. -- Primary error position
:
:
:::
:
:
:
print
Configuration
local mu = require
local cfg = mu.
: -- Enable compact mode
: -- Draw arrows across line gaps
: -- Tab expansion width
: -- Truncate long lines to 80 columns
: -- Use Unicode box-drawing characters
: -- Use character offsets (vs "byte")
: -- Ambiguous character width (1 or 2)
mu.
:
-- ... rest of report
Multi-Source Files
local mu = require
mu.
:: -- src_id=1, first source
:: -- src_id=2, second source
:
:
:
File Sources (C Bindings Only)
local mu = require
local io = require
local fp = io.
mu.
: -- Streams file on-demand
::
:
Notice that if you use file handle on Windows, the musubi.so must not be built as static linking (/MT).
API Reference
Report Builder
| Method | Description |
|---|---|
mu.report(pos, src_id?) |
Create a new report at position pos |
:title(level, message) |
Set report level ("Error", "Warning") and title |
:code(code) |
Set optional error code (e.g., "E0308") |
:label(start, end?, src_id?) |
Add a label span (half-open interval [start, end)) |
:message(text, width?) |
Attach message to the last added label |
:color(color) |
Set color for the last added label |
:order(n) |
Set display order for the last label |
:priority(n) |
Set priority for clustering |
:note(text) |
Add a note to the footer |
:help(text) |
Add a help message to the footer |
:source(content, name?, offset?) |
Register a source (string or FILE*) with line offset (0 default) |
:render(writer?) |
Render the report (returns string or calls writer function) |
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
compact |
boolean | false |
Hide empty lines between labels |
cross_gap |
boolean | true |
Draw arrows across skipped lines |
underlines |
boolean | true |
Draw underlines for single-line labels |
multiline_arrows |
boolean | true |
Use arrows for multi-line spans |
tab_width |
integer | 4 |
Number of spaces per tab |
limit_width |
integer | 0 |
Max line width (0 = unlimited) |
ambiwidth |
integer | 1 |
Width of ambiguous Unicode characters |
label_attach |
string | "middle" |
Label attachment point ("start", "middle", "end") |
index_type |
string | "char" |
Position indexing ("char" or "byte") |
char_set |
string | "unicode" |
Glyph set ("unicode" or "ascii") |
color |
boolean | true |
Enable ANSI color codes |
Color Generator
local cg = mu. -- min_brightness ∈ [0, 1], default 0.5
local color_func = cg: -- Get next color in cycle
Architecture
Rendering Pipeline
Report:render()
├─ Context Creation (group labels by source, calculate widths)
├─ Header Rendering (error level, code, message)
├─ For each source group:
│ ├─ Reference Header (file:line:col)
│ ├─ Line Rendering:
│ │ ├─ Label Clustering (group overlapping labels)
│ │ ├─ Window Calculation (when limit_width > 0)
│ │ ├─ Virtual Row Splitting (multi-line labels)
│ │ └─ For each cluster:
│ │ ├─ Line Content (with label highlighting)
│ │ └─ Arrow Drawing (underlines, connectors, messages)
│ └─ Empty Line
└─ Footer Rendering (notes, help messages)
Key Design Decisions
Intervals:
- All position named
start/enduse half-open intervals[start, end) - All position named
first/lastuse close intervals[fist, last]
Width Caching:
- Pre-compute cumulative display widths for each line
- Binary search (
muC_widthindex) for O(log n) position lookups - Handles UTF-8 multi-byte characters, Emoji, RI, CJK double-width, tabs
Label Clustering:
- Group overlapping/nearby labels into virtual rows
- Separate inline labels (single line) from multiline labels
- Dynamic column range calculation for windowing
Memory Management (C):
- Caller provides allocator function (defaults to
malloc/free) - Dynamic arrays with geometric growth (muA_* macros)
- External pointers (messages, source names) must outlive render call
Testing
Running Tests
Lua Implementation:
REF=1
C Implementation:
# Compile with coverage
# Run tests (uses C bindings by default)
# Generate coverage report
Test Coverage
Both implementations maintain 100% test coverage:
- 95 test cases covering all rendering paths
- Edge cases: zero-width spans, CJK characters, tab expansion, window truncation
- Regression tests for all fixed bugs
- Pixel-perfect output verification (2400+ lines of expected output)
Test Categories:
- Basic rendering (labels, messages, colors)
- Multi-line spans and clustering
- Line width limiting and windowing
- Unicode and CJK character handling
- Configuration options (compact, cross_gap, etc.)
- Multi-source file support
- File streaming (C only)
Implementation Notes
Differences from Rust Ariadne
Improvements:
- Cleaner margin render handling
- Explicit virtual row rendering for multi-line labels
- Width-based windowing with binary search optimization
- Label without message supports
Limitations:
- Only supports
\nnewlines (not Unicode line separators) - Not full UAX#29 grapheme cluster breaking (only support ZWJ & RI now)
C Port Details
See .github/c_port.md for detailed implementation notes:
- API constraints and call ordering requirements
- Memory management and lifetime rules
- UTF-8 handling and Unicode width calculations
- Source lifecycle and file streaming
- Known limitations and edge cases
Project Structure
See .github/project-structure.md for:
- Detailed architecture documentation
- Data structure definitions
- Rendering algorithm explanations
- Bug fix history and rationale
Contributing
Contributions are welcome! Please:
- Run tests before submitting:
lua test.lua - Maintain 100% coverage: Add tests for new features
- Follow existing style: Lua uses tabs, C uses 4 spaces
- Update documentation: Keep README and .github/*.md in sync
Development Workflow
# Run tests with coverage
# Find uncovered lines
# Run specific test
License
MIT License - See LICENSE for details.
Credits
- Original Ariadne library: zesterer/ariadne
- UTF-8 support: starwing/luautf8
- Test framework: LuaUnit
Related Projects
- Ariadne (Rust) - Original implementation
- Annotate Snippets (Rust) - Similar project
- Miette (Rust) - Fancy diagnostics library
- Codespan (Rust) - Alternative approach
Made with ❤️ for better compiler diagnostics