Lumis
Syntax highlighter powered by Tree-sitter and Neovim themes.
Features
- 110+ Tree-sitter languages - Fast, accurate, and updated syntax parsing
- 250+ built-in Neovim themes - Updated and curated themes from the Neovim community
- Built-in formatters - HTML (inline/linked), Terminal (ANSI), Multi-theme (light/dark), BBCode
- Custom formatters - Build your own output
- Language auto-detection - File extension, shebang, and emacs-mode support
- Line highlighting - Mark and style individual lines, with custom HTML wrappers
- Streaming-friendly - Handles incomplete code
- Load parsers on demand - Verified and compiled, including injected languages
Installation
def deps do
[
{:lumis, "~> 0.7"}
]
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. See custom formatters.
Parsers
A parser is an ordinary dependency: add {:lumis_wasm_elixir, "~> 0.26.0"} and
mix deps.get delivers the bytes. Highlighting verifies and 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.
# 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, see
where the precompiled NIF comes from.
Documentation
- Elixir integration — configuration, releases, Phoenix
- Formatters — every formatter and its options
- Custom formatters — render the event stream yourself
- Annotations — compose your own ranges into the event stream
- Themes — the theme list, custom themes, CSS files
- Languages — what is supported and how detection works
- Line highlighting
- Line numbers
- Recipes — LiveView rendering, light/dark, injected languages
API reference: hexdocs.pm/lumis.