Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

mdbook-tsitter

Tree-sitter syntax highlighting for mdBook — for any language you can point a grammar at.

View the project on GitHub.

mdbook-tsitter is an mdBook preprocessor that parses fenced code blocks with tree-sitter and renders the resulting captures as themeable HTML. It is grammar-agnostic: supply a compiled parser and the grammar’s highlight queries, and the same preprocessor can handle anything from Rust and Lua to a language of your own.

Features

  • Grammar-agnostic. Use any tree-sitter grammar that can be built as a shared library.
  • Structural highlighting. Definitions, calls, types, parameters, properties, macros, and other syntax can receive distinct styles whenever the grammar’s queries distinguish them.
  • Embedded languages. Injection queries can highlight fenced code inside Markdown, languages embedded in strings, and other nested syntax.
  • Themeable capture classes. Captures become predictable ts-… CSS classes, with a bundled stylesheet that follows mdBook’s light, rust, coal, navy, and ayu themes.
  • Language aliases and locals queries. Match several fence names to one grammar and opt into scope-aware query behavior where the grammar supports it.
  • Selective processing. Leave individual blocks to mdBook when you need its built-in Rust Playground behavior or other native code-block features.
  • Build-time rendering. Highlighted HTML is generated with the book; the reader does not need a tree-sitter runtime.

The example book includes a custom Macaulay2 grammar and substantial side-by-side examples in Rust, Python, TypeScript, JavaScript, Go, Java, C, C++, Bash, PHP, Lua, and Haskell. Its complete source lives in examples/languages.

Installation

Install the preprocessor from crates.io:

cargo install mdbook-tsitter

The mdbook-tsitter binary must be available on PATH when mdbook build runs. Each configured language also needs:

  1. A compiled tree-sitter parser (.so, .dylib, or .dll).
  2. Its queries/highlights.scm file.
  3. Optionally, injections.scm and locals.scm.

See Getting parsers and queries for common ways to obtain them.

Quick start

Generate the default stylesheet in your book:

mkdir -p theme
mdbook-tsitter css > theme/treesitter.css

Place a parser and its highlight query in your project, then configure book.toml:

[preprocessor.tsitter]

[preprocessor.tsitter.languages.rust]
library = "parsers/rust.so"
highlights = "queries/rust/highlights.scm"

[output.html]
additional-css = ["theme/treesitter.css"]

Paths are relative to the book root. After that, ordinary fenced blocks for the configured language are highlighted automatically:

```rust
fn main() {
    println!("highlighted at build time");
}
```

Build the book as usual:

mdbook build

Languages that are not configured, blocks without a language tag, and blocks that opt out are left untouched for mdBook to handle.

Configuring languages

Everything lives under [preprocessor.tsitter].

Preprocessor options

keydefaultmeaning
injecttrueHighlight embedded languages using configured injection queries. Only configured languages are used. Set this to false to disable injections globally.

Language options

Add one [preprocessor.tsitter.languages.<name>] table per grammar:

keyrequiredmeaning
libraryyesPath to the compiled parser shared library.
highlightsyesPath to the grammar’s highlights query.
symbolnoParser constructor symbol; defaults to tree_sitter_<name> with - changed to _.
injectionsnoPath to an injections query for embedded languages.
localsnoPath to a locals query for scope-aware highlighting.
aliasesnoAdditional code-fence names handled by this grammar; defaults to the table key.

A fuller Markdown configuration might look like this:

[preprocessor.tsitter.languages.markdown]
library = "parsers/markdown.so"
highlights = "queries/markdown/highlights.scm"
injections = "queries/markdown/injections.scm"
aliases = ["md", "markdown"]

Injection queries can select another configured grammar using standard @injection.language and @injection.content captures. All grammars share the same capture-class table during a build, so embedded and host languages use the same theme consistently.

Leaving a block to mdBook

Processing a block replaces its Markdown with highlighted HTML. Add the notreesitter annotation when a block should retain mdBook’s own handling, including Rust Playground buttons, hidden lines, and ignore or no_run annotations:

```rust,notreesitter
# fn main() {
let runnable = "mdBook keeps control of this block";
# }
```

Getting parsers and queries

Most grammars live in a tree-sitter-<language> repository. With the tree-sitter CLI, a typical parser build looks like:

git clone https://github.com/<owner>/tree-sitter-nix
cd tree-sitter-nix
tree-sitter build --output libtree-sitter-nix.so

Use .dylib on macOS or .dll on Windows, and point library at the exact output path. The extension is never assumed.

The highlight query is usually the grammar’s queries/highlights.scm. An existing nvim-treesitter installation is another convenient source of compiled parsers and queries.

The setup script in examples/languages/setup.sh shows one way to stage several parsers and their queries for a complete book.

Theming

The generated stylesheet covers standard tree-sitter and nvim-treesitter capture names and adapts to mdBook’s built-in colour schemes. Edit it directly or add a later stylesheet with your overrides.

Each capture gets the ts- prefix, dots become hyphens, and every prefix is emitted so broad rules can cascade into more specific ones:

capturegenerated classes
keywordts-keyword
keyword.operatorts-keyword ts-keyword-operator
string.regexpts-string ts-string-regexp

For example:

.ts-comment {
  font-style: italic;
}

code.language-rust .ts-function-macro {
  color: var(--ts-purple);
}

There is no fixed capture list in the preprocessor. Capture names come directly from each grammar’s queries; names beginning with _ are treated as internal and left unstyled.

How it works

mdBook passes every chapter to the preprocessor as Markdown. mdbook-tsitter uses the same Markdown parser as mdBook (pulldown-cmark), locates top-level fenced code blocks for configured languages, and sends their source through tree-sitter-highlight.

The highlighted events become semantic CSS classes and are spliced back into the chapter as ready-made HTML:

<pre class="treesitter"><code class="no-highlight language-rust">…spans…</code></pre>

The original language class remains available for per-language styles, while no-highlight marks the HTML as already processed.

Development

Run the Rust test suite:

cargo test

Build the multi-language example book:

./examples/languages/setup.sh
mdbook build examples/languages

The parsers and queries used by the deployed example are committed so the hosted build is reproducible. The setup script is useful when refreshing those assets from local grammar installations.

License

Licensed under either of MIT or Apache-2.0 at your option.