shiki
Fast TextMate highlighting for Rust.
shiki is the core crate of shiki-rs. It compiles TextMate grammars into a
compact runtime representation, tokenizes with Oniguruma, resolves themes, and
provides HTML and custom rendering APIs.
This crate intentionally contains no bundled language or theme catalog. Use
shiki-langs and
shiki-themes, provide runtime
definitions, or generate an engine with
shiki-macros.
Installation
cargo add shiki
For the usual bundled setup:
cargo add shiki shiki-langs shiki-themes
Quick start
use ;
static LANGUAGES: LanguageBundle = languages!;
let mut highlighter = builder
.bundle
.languages
.theme
.build?;
let html = highlighter.code_to_html?;
Plain text
Every engine includes the reserved text language and its txt and plain
aliases. It emits one token for each non-empty line, bypassing syntax matching
while retaining HTML escaping and the selected theme's default foreground and
background styles. This makes it suitable as the fallback for an unavailable
or user-provided language:
let html = highlighter.code_to_html?;
A standalone plain-text engine does not require a language bundle:
let mut highlighter = builder
.languages
.theme
.build?;
ANSI terminal output
The reserved ansi language converts terminal SGR sequences to themed tokens
and HTML without requiring a TextMate grammar:
let html = highlighter.code_to_html?;
It supports the standard and bright theme palettes, 256-color and TrueColor
sequences, foreground/background colors, reverse video, dim, bold, italic,
underline, strikethrough, and state spanning multiple lines. Theme JSON
terminal.ansi* colors are retained in runtime and precompiled engines;
VS Code-compatible colors are used when a theme omits them. Owned ANSI tokens
have no TextMate scopes and use scope ID 0; the scope-token and incremental
grammar APIs remain specific to TextMate languages.
Build a shared engine when several documents need the same immutable grammar and theme data:
let engine = builder
.bundle
.languages
.theme
.build_engine?;
let mut first = engine.session?;
let mut second = engine.session?;
Main types
HighlighterBuilderselects definitions, themes, and regex limits.HighlighterEngineowns shared immutable compiled grammar and theme data.Highlighterlazily owns per-language tokenizers and rendering caches.LanguageSessionowns one independent document tokenizer.GrammarStatecarries TextMate begin/end and begin/while state between lines.HtmlRendererstreams compact HTML without materializing owned theme tokens.Rendereris the extension point for other output formats.
Tokenization APIs
Highlighter exposes representations for different integration costs:
tokenize_linefor incremental documents;tokenize_line_intofor allocation-reusing streaming tokenization;token_stylesfor borrowed multi-theme style resolution;code_to_scope_tokensfor source ranges and compact scope IDs;code_to_tokensfor owned tokens resolved against one theme;code_to_tokens_with_themesfor every configured theme;code_to_htmlandcode_to_html_with_optionsfor streaming HTML;renderfor a customRendererimplementation.
split_lines, ThemeInfo, and ansi::AnsiParser expose the same line,
theme, and ANSI primitives used by the built-in renderer. Internal grammar IR
and tokenizer storage stay private so custom renderers do not depend on cache
layout details.
Scope names can be recovered from ScopeStackId when diagnostics or semantic
inspection needs them. cache_stats, clear_language_cache, and
clear_all_caches expose control over lazily accumulated runtime state.
HTML rendering
HtmlOptions independently controls explicit classes and attributes, automatic
shiki/theme classes, data-themes, line wrappers, root theme variables,
foreground/background styles, default theme selection, token spans, and
variable-only output.
let mut options = default;
options.default_theme = Some;
options.pre_classes.push;
options.code_classes.push;
options
.pre_attributes
.insert;
options.include_line_wrapper = false;
options.include_default_theme_styles = false;
let html = highlighter.code_to_html_with_options?;
Use HtmlOptions::clean() for a <pre><code>…</code></pre> wrapper with no
automatic wrapper attributes or root styles. It retains the line class and
syntax-highlighted token styles.
Custom options own their Vec<String> and BTreeMap<String, String> values.
Use LazyLock to create one shared global configuration:
use LazyLock;
static HTML: = new;
Classes, attributes, line_class, and default_theme are public fields. Set
line_class to None to keep a classless line wrapper, or set
include_line_wrapper to false to remove the wrapper itself.
With multiple themes, token colors are emitted as CSS custom properties. Font and background variables are emitted only when required. Adjacent tokens with the same final visual style are merged even when their scope stacks differ; plain whitespace is left unwrapped when a span would have no visual effect.
Runtime definitions
The default json feature enables TextMate grammar and theme JSON:
let highlighter = builder
.json_language?
.json_theme?
.build?;
Definitions may also be constructed through RawGrammar, RawRule,
RawTheme, and their nested raw types. LanguageInput adds aliases and
external injection targets to runtime grammars.
Features
| Feature | Default | Description |
|---|---|---|
json |
yes | Enables serde/serde_json, runtime JSON parsing, and JSON-backed definitions |
Compile without the JSON stack when the target only consumes snapshots generated
by shiki-macros:
cargo add shiki --no-default-features
In that configuration, target-side shiki depends on onig_sys, thiserror,
and the small lz4_flex snapshot codec, but not serde or serde_json.
Performance model
Compiled grammar IR and themes belong to the engine. Engines produced by
shiki-macros decode each grammar IR on first use; runtime-built engines compile
their selected grammars eagerly. Oniguruma scanners, dynamic regexes, scope
transitions, injection results, and style rows are created lazily in a
highlighter or session. HighlighterEngine::loaded_language_count exposes the
number of resident grammar IRs.
- Reuse engines and sessions.
- Bundle only required roots when possible.
- Use incremental line state for editors.
- Prefer scope tokens or streaming rendering when owned tokens are unnecessary.
- Expect very long minified lines to cost more than ordinary line-oriented source.