#tree-sitter #grammar #syntax-highlighting #wasm

dev arborium-highlight

Unified syntax highlighting for arborium - works with both static Rust grammars and WASM plugins

41 stable releases

Uses new Rust 2024

new 2.13.0 Feb 9, 2026
2.12.4 Jan 18, 2026
2.4.7 Dec 30, 2025
1.3.0 Dec 13, 2025
0.1.0 Dec 8, 2025

#2158 in Text processing

Download history 1681/week @ 2025-12-09 559/week @ 2025-12-16 810/week @ 2025-12-23 1351/week @ 2025-12-30 1174/week @ 2026-01-06 1395/week @ 2026-01-13 1794/week @ 2026-01-20 990/week @ 2026-01-27 1235/week @ 2026-02-03

5,562 downloads per month
Used in 114 crates (3 directly)

MIT/Apache

300KB
6.5K SLoC

Unified syntax highlighting for arborium.

This crate provides the core highlighting engine that works with both:

  • Statically linked Rust grammars: For CLI tools and servers
  • Dynamically loaded WASM plugins: For browser contexts

Why Async in a Highlighting Library?

You might wonder why a syntax highlighting library has async code. The answer is browser support.

  • Parsing is synchronous: Tree-sitter parsing cannot be async—it's a fundamentally synchronous operation that walks the syntax tree.

  • Getting a grammar can be async: In browser contexts, grammar plugins are loaded from a CDN via JavaScript's dynamic import(). This is inherently async since it involves network requests and WASM instantiation.

In native Rust, grammars are statically linked, so the provider returns immediately. But the trait is async to support both use cases with the same code.

Architecture

The highlighting system is built around two key traits:

  • Grammar: What a grammar can do — parse text and return spans
  • GrammarProvider: How grammars are obtained — this is where sync vs async differs

The Sync-in-Async-Clothing Pattern

The core highlighting logic (including injection handling) is written once as async code in HighlighterCore. Two wrappers provide the sync and async APIs:

  • SyncHighlighter: Polls the async future once and panics if it yields. This is safe for native Rust where providers return immediately.

  • AsyncHighlighter: Actually awaits provider calls. Use this for browser/WASM contexts where grammar loading involves network requests.

This design ensures both environments share the exact same injection-handling logic, avoiding subtle bugs from duplicated code.

When to Use Which

Context Highlighter Provider Example
Native Rust SyncHighlighter StaticProvider (grammars compiled in)
Browser WASM AsyncHighlighter JsGrammarProvider (loads from CDN)

Quick Start

use arborium_highlight::{SyncHighlighter, Grammar, GrammarProvider, ParseResult, Span};
use arborium_highlight::{HighlightConfig, HtmlFormat};

// Define your grammar (implements Grammar trait)
struct MyGrammar { /* ... */ }
impl Grammar for MyGrammar {
    fn parse(&mut self, text: &str) -> ParseResult {
        // Parse and return spans + injections
        ParseResult::default()
    }
}

// Define your provider (implements GrammarProvider trait)
struct MyProvider { /* ... */ }
impl GrammarProvider for MyProvider {
    type Grammar = MyGrammar;
    async fn get(&mut self, language: &str) -> Option<&mut Self::Grammar> {
        // Return grammar for language
        None
    }
}

// Use with default configuration (custom elements: <a-k>, <a-f>, etc.)
let mut highlighter = SyncHighlighter::new(MyProvider { /* ... */ });
let html = highlighter.highlight("rust", "fn main() {}");
// Output: <a-k>fn</a-k> <a-f>main</a-f>() {}

// Or use class-based output for compatibility with existing CSS
let config = HighlightConfig {
    html_format: HtmlFormat::ClassNames,
    ..Default::default()
};
let mut highlighter = SyncHighlighter::with_config(MyProvider { /* ... */ }, config);
let html = highlighter.highlight("rust", "fn main() {}");
// Output: <span class="keyword">fn</span> <span class="function">main</span>() {}

HTML Output Formats

Arborium supports multiple HTML output formats via HtmlFormat:

  • CustomElements (default): Compact custom elements like <a-k>, <a-f>, etc.
  • CustomElementsWithPrefix(prefix): Custom elements with your prefix, e.g., <code-k>
  • ClassNames: Traditional <span class="keyword"> for compatibility
  • ClassNamesWithPrefix(prefix): Namespaced classes like <span class="arb-keyword">

See HtmlFormat for examples and use cases.


arborium-highlight

Core syntax highlighting engine for arborium.

Features

This crate provides the unified highlighting engine that works with both:

  • Statically linked Rust grammars: For CLI tools and servers
  • Dynamically loaded WASM plugins: For browser contexts

Why Async?

The parsing is synchronous (tree-sitter fundamentally is), but getting a grammar can be async in browser contexts where plugins are loaded from a CDN via dynamic import(). The async trait supports both use cases with the same API.

Usage

use arborium_highlight::{Span, spans_to_html, HtmlFormat};

// After getting spans from a grammar...
let html = spans_to_html(source, spans, &HtmlFormat::CustomElements);

Part of the arborium project. See arborium.bearcove.eu for more information.

Dependencies

~0–12MB
~114K SLoC