Lumis

Syntax highlighter powered by Tree-sitter and Neovim themes.

https://lumis.sh

Hex Version Hex Docs MIT

Features

Installation

Add Lumis and a parser for each language you highlight:

def deps do
[
{:lumis, "~> 0.9"},
{:lumis_wasm_elixir, "~> 0.26.0"}
]
end

Usage

iex> Lumis.highlight!("Atom.to_string(:elixir)", formatter: {:html_inline, language: "elixir", theme: "github_light"})

The language is optional — Lumis detects it from the source, a filename, or a shebang. The theme is optional too, but there is no default: without one, :html_inline emits spans with no colors. Themes are named: theme: "github_light", or a Lumis.Theme struct built from your own JSON.

Formatters decide the output: :html_inline, :html_linked, :html_multi_themes, :terminal, :bbcode_scoped, or your own.

For your own, implement Lumis.Formatter and build the output with Lumis.Formatter.HTML or Lumis.Formatter.ANSI, which hold the same pieces the built-in formatters use.

Parsers

A parser is an ordinary dependency: add {:lumis_wasm_elixir, "~> 0.26.0"} and mix deps.get delivers the bytes. Highlighting loads whatever a document needs, including languages injected inside it, and keeps them for every later request. Loading is global to the VM, so only the first process pays.

A language no dependency supplies is not fetched. A document's own language missing is an error — Lumis.ParserError with the package to add — and a language injected inside it missing costs that block its highlighting, not the document. So add the ones a document can inject too, not only the ones it names: Markdown fences reach whatever language they label, HTML reaches css and javascript, and Elixir reaches comment. A bundle package installs a set at once, such as {:lumis_wasm_bundle_web, "~> 0.1"}, and the language catalog at docs.lumis.sh lists every package name.

# move the compile off the first request
Lumis.Languages.load(["elixir", "html", "javascript", "css"])

Application startup

Warm parsers from your application's start/2 so production does not compile them on the first request:

def start(_type, _args) do
Lumis.Languages.async_load(~w(elixir html javascript css))
Supervisor.start_link(children(), strategy: :one_for_one, name: MyApp.Supervisor)
end

It returns immediately, so the boot never waits on a compile, and a failed warm-up is logged rather than able to stop the application from starting.

See the deployment guide for the full lifecycle example, bundles, and custom data directories.

The NIF is precompiled. Set LUMIS_BUILD=1 to build it from source instead, or LUMIS_USE_LEGACY_ARTIFACTS=1 to take the legacy-CPU variant on a machine without the newer instruction sets.

It downloads from GitHub Releases, mirrored to Cloudflare R2. Set config :lumis, artifact_source: :cloudflare or LUMIS_ARTIFACT_SOURCE=cloudflare to use the mirror when GitHub is down.

Documentation

Guides for configuration, releases, Phoenix, formatters, themes and recipes are at docs.lumis.sh.

API reference: hexdocs.pm/lumis.

Acknowledgements