# `Lumis`
[🔗](https://github.com/leandrocp/lumis/blob/hex-lumis/v0.10.0/packages/elixir/lumis/lib/lumis.ex#L1)

<p align="center">
  Syntax highlighter powered by Tree-sitter and Neovim themes.
</p>

<p align="center">
  <a href="https://lumis.sh">https://lumis.sh</a>
</p>

<div align="center">
  <a href="https://hex.pm/packages/lumis">
    <img alt="Hex Version" src="https://img.shields.io/hexpm/v/lumis">
  </a>

  <a href="https://hexdocs.pm/lumis">
    <img alt="Hex Docs" src="http://img.shields.io/badge/hex.pm-docs-green.svg?style=flat">
  </a>

  <a href="https://opensource.org/licenses/MIT">
    <img alt="MIT" src="https://img.shields.io/hexpm/l/lumis">
  </a>
</div>

## 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
- **Parsers are dependencies** - Declared in `mix.exs`, compiled on first use

## Installation

Add Lumis and a parser for each language you highlight:

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

## Usage

```elixir
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](https://docs.lumis.sh) lists every package name.

```elixir
# 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:

```elixir
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](https://lumis.hexdocs.pm/deployment.html) 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](https://docs.lumis.sh).

API reference: [hexdocs.pm/lumis](https://hexdocs.pm/lumis).

## Acknowledgements

* [Makeup](https://hex.pm/packages/makeup) for setting up the baseline and for the inspiration
* [Inkjet](https://crates.io/crates/inkjet) for the Rust implementation up to v0.2 and for the inspiration

# `bbcode_highlight_lines`

```elixir
@type bbcode_highlight_lines() :: %{lines: [pos_integer() | Range.t()]} | nil
```

Highlight lines options for the BBCode formatter.

# `formatter`

```elixir
@type formatter() ::
  :html_inline
  | {:html_inline,
     language: language(),
     structure: html_structure(),
     theme: theme(),
     pre_class: String.t(),
     pre_attrs: html_attrs(),
     code_attrs: html_attrs(),
     italic: boolean(),
     include_highlights: boolean(),
     highlight_lines: html_inline_highlight_lines(),
     line_numbers: boolean(),
     header: header()}
  | :html_linked
  | {:html_linked,
     language: language(),
     structure: html_structure(),
     pre_class: String.t(),
     pre_attrs: html_attrs(),
     code_attrs: html_attrs(),
     highlight_lines: html_linked_highlight_lines(),
     line_numbers: boolean(),
     header: header()}
  | :html_multi_themes
  | {:html_multi_themes,
     language: language(),
     structure: html_structure(),
     themes: keyword(theme()),
     default_theme: String.t(),
     css_variable_prefix: String.t(),
     pre_class: String.t(),
     pre_attrs: html_attrs(),
     code_attrs: html_attrs(),
     italic: boolean(),
     include_highlights: boolean(),
     highlight_lines: html_inline_highlight_lines(),
     line_numbers: boolean(),
     header: header()}
  | :terminal
  | {:terminal,
     language: language(),
     theme: theme(),
     background: :theme | String.t() | nil,
     width: pos_integer() | nil,
     highlight_lines: terminal_highlight_lines(),
     line_numbers: boolean()}
  | :bbcode_scoped
  | {:bbcode_scoped,
     language: language(), highlight_lines: bbcode_highlight_lines()}
  | module()
  | {module(), keyword()}
```

Highlighter formatter and its options.

Available formatters: `:html_inline`, `:html_linked`, `:html_multi_themes`, `:terminal`, `:bbcode_scoped`

* `:html_inline` - generates `<span>` tags with inline styles for each token, for example: `<span style="color: #6eb4bff;">Atom</span>`.
* `:html_linked` - generates `<span>` tags with `class` representing the token type, for example: `<span class="l-keyword-special">Atom</span>`.
   Must link an external CSS in order to render colors, see more at [HTML Linked](https://hexdocs.pm/lumis/Lumis.html#module-html-linked).
* `:html_multi_themes` - generates HTML with CSS custom properties (variables) for multiple themes, enabling light/dark mode support.
   Inspired by [Shiki Dual Themes](https://shiki.style/guide/dual-themes).
* `:terminal` - generates ANSI escape codes for terminal output.
* `:bbcode_scoped` - generates nested BBCode tags using highlight scope names, for example: `[keyword-elixir]defmodule[/keyword-elixir]`.

You can either pass the formatter as an atom to use default options or a tuple with the formatter name and options, so both are equivalent:

    # passing only the formatter name like below:
    :html_inline
    # is the same as passing an empty list of options:
    {:html_inline, []}

A custom formatter can be any module implementing `Lumis.Formatter`, passed
either directly or with options:

    {MyFormatter, language: "elixir"}

## Available Options:

* `html_inline`:

    - `:language` (`t:language/0` - default: `nil`) - the language used by the formatter. When omitted, Lumis tries to auto-detect it from the source.
    - `:structure` (`t:html_structure/0` - default: `:block`) - `:block` writes a `<pre><code>` block; `:inline` writes only the token spans, for markup the page already owns, and ignores the options that configure a block.
    - `:theme` (`t:theme/0` - default: `nil`) - the theme to apply styles on the highlighted source code.
    - `:pre_class` (`t:String.t/0` - default: `nil`) - the CSS class to append into the wrapping `<pre>` tag.
    - `:pre_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the wrapping `<pre>` tag.
    - `:code_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the nested `<code>` tag.
    - `:italic` (`t:boolean/0` - default: `false`) - enable italic style for the highlighted code.
    - `:include_highlights` (`t:boolean/0` - default: `false`) - include the highlight scope name in a `data-highlight` attribute. Useful for debugging.
    - `:highlight_lines` (`t:html_inline_highlight_lines/0` - default: `nil`) - highlight specific lines either using the theme `highlighted` style or with custom CSS styling.
    - `:line_numbers` (`t:boolean/0` - default: `false`) - open each line with a `<span class="l-line-number">` gutter carrying its number.
    - `:header` (`t:header/0` - default: `nil`) - wrap the highlighted code with custom open and close HTML tags.

* `html_linked`:

    - `:language` (`t:language/0` - default: `nil`) - the language used by the formatter. When omitted, Lumis tries to auto-detect it from the source.
    - `:structure` (`t:html_structure/0` - default: `:block`) - `:block` writes a `<pre><code>` block; `:inline` writes only the token spans, for markup the page already owns, and ignores the options that configure a block.
    - `:pre_class` (`t:String.t/0` - default: `nil`) - the CSS class to append into the wrapping `<pre>` tag.
    - `:pre_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the wrapping `<pre>` tag.
    - `:code_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the nested `<code>` tag.
    - `:highlight_lines` (`t:html_linked_highlight_lines/0` - default: `nil`) - highlight specific lines either using the `l-highlighted` class from themes or with a custom CSS class.
    - `:line_numbers` (`t:boolean/0` - default: `false`) - open each line with a `<span class="l-line-number">` gutter carrying its number.
    - `:header` (`t:header/0` - default: `nil`) - wrap the highlighted code with custom open and close HTML tags.

* `html_multi_themes`:

    - `:language` (`t:language/0` - default: `nil`) - the language used by the formatter. When omitted, Lumis tries to auto-detect it from the source.
    - `:structure` (`t:html_structure/0` - default: `:block`) - `:block` writes a `<pre><code>` block; `:inline` writes only the token spans, for markup the page already owns, and ignores the options that configure a block.
    - `:themes` (`keyword(theme())` - required) - keyword list of theme identifiers to theme names/structs. Theme identifiers become CSS class names and CSS variable prefixes. Example: `[light: "github_light", dark: "github_dark"]`.
    - `:default_theme` (`t:String.t/0` - default: `nil`) - controls inline color rendering: specify a theme identifier for inline colors, use `"light-dark()"` for CSS light-dark() function, or `nil` for CSS variables only.
    - `:css_variable_prefix` (`t:String.t/0` - default: `nil`) - CSS variable prefix (defaults to `"--lumis"` if nil). Generates variables like `--lumis-light` (color), `--lumis-light-bg` (background), `--lumis-light-font-style`, etc.
    - `:pre_class` (`t:String.t/0` - default: `nil`) - the CSS class to append into the wrapping `<pre>` tag.
    - `:pre_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the wrapping `<pre>` tag.
    - `:code_attrs` (`t:html_attrs/0` - default: `[]`) - attributes merged into the nested `<code>` tag.
    - `:italic` (`t:boolean/0` - default: `false`) - enable italic style for the highlighted code.
    - `:include_highlights` (`t:boolean/0` - default: `false`) - include the highlight scope name in a `data-highlight` attribute.
    - `:highlight_lines` (`t:html_inline_highlight_lines/0` - default: `nil`) - highlight specific lines (same as html_inline).
    - `:line_numbers` (`t:boolean/0` - default: `false`) - open each line with a `<span class="l-line-number">` gutter carrying its number.
    - `:header` (`t:header/0` - default: `nil`) - wrap the highlighted code with custom open and close HTML tags.

* `terminal`:

    - `:language` (`t:language/0` - default: `nil`) - the language used by the formatter. When omitted, Lumis tries to auto-detect it from the source.
    - `:theme` (`t:theme/0` - default: `nil`) - the theme to apply styles on the highlighted source code.
    - `:background` (`:theme | t:String.t/0 | nil` - default: `nil`) - fallback background behavior: `nil` inherits the output background, `:theme` uses the theme's normal background color, and a string uses that color.
    - `:width` (`pos_integer() | nil` - default: `nil`) - pad each rendered terminal line to the given width. This is most useful with `:background`.
    - `:highlight_lines` (`t:terminal_highlight_lines/0` - default: `nil`) - paint specific lines with a background colour, either `:background` or the theme's `highlighted` background.
    - `:line_numbers` (`t:boolean/0` - default: `false`) - prefix each line with its number, right-aligned to the widest one and dimmed with the theme's `comment` colour.

* `bbcode_scoped`:

    - `:language` (`t:language/0` - default: `nil`) - available when passed as `{:bbcode_scoped, ...}`.
    - `:highlight_lines` (`t:bbcode_highlight_lines/0` - default: `nil`) - wrap specific lines in `[highlighted]...[/highlighted]`.

## Examples

### Inline HTML formatter with default options

    :html_inline

There is no default theme, so this emits `<span>` tags without any `style` attribute.
Pass `:theme` to get colors, or use `:html_linked` to style the output with a CSS file.

### Inline HTML formatter with custom options

    {:html_inline, theme: "onedark", pre_class: "example-01", include_highlights: true}

### HTML Inline: highlight specific lines

    # apply theme's `highlighted` style
    {:html_inline, theme: "onedark", highlight_lines: %{lines: [2..4, 6], style: :theme}}

    # style: :theme is the default
    {:html_inline, theme: "onedark", highlight_lines: %{lines: [1, 2, 3]}}

    # explicitly use theme style
    {:html_inline, theme: "onedark", highlight_lines: %{lines: [1, 2, 3], style: :theme}}

    # overrides default style
    {:html_inline, theme: "onedark", highlight_lines: %{lines: [1, 3..5, 8], style: "background-color: #fff3cd; border-left: 3px solid #ffc107;"}}

    # with only class and no style
    {:html_inline, theme: "onedark", highlight_lines: %{lines: [1, 2, 3], style: nil, class: "transition-colors duration-500 w-full inline-block bg-yellow-500"}}

### HTML Linked: highlight specific lines

    # use default `l-highlighted` class (already present in themes)
    {:html_linked, highlight_lines: %{lines: [2..4, 6]}}

    # use custom class
    {:html_linked, highlight_lines: %{lines: [1, 2, 3], class: "error-line"}}

### Inline structure: highlight code inside a sentence

    # only the token spans, to put inside a `<code>` the page already has
    {:html_inline, theme: "onedark", structure: :inline}

### Wrap with custom open and close HTML tags

    header = %{
      open_tag: "<div class="code-header"><span>file: app.ex</span>",
      close_tag: "</div>"
    }
    {:html_inline, theme: "onedark", header: header}

### HTML Multi-Themes: Light/Dark mode support

    # Basic dual theme with CSS variables
    {:html_multi_themes, themes: [light: "github_light", dark: "github_dark"]}

    # With light-dark() function for automatic theme switching based on system preference
    {:html_multi_themes,
     themes: [light: "github_light", dark: "github_dark"],
     default_theme: "light-dark()"}

    # With inline colors for default theme and CSS variables for others
    {:html_multi_themes,
     themes: [light: "github_light", dark: "github_dark"],
     default_theme: "light"}

    # Multiple themes with custom prefix
    {:html_multi_themes,
     themes: [light: "github_light", dark: "github_dark", dim: "catppuccin_frappe"],
     css_variable_prefix: "--code"}

    # With Theme structs instead of strings
    light_theme = Lumis.Theme.get("github_light")
    dark_theme = Lumis.Theme.get("github_dark")
    {:html_multi_themes, themes: [light: light_theme, dark: dark_theme]}

### Terminal formatter

    :terminal

    {:terminal, theme: "github_light"}

    {:terminal, theme: "dracula", background: :theme, width: 120}

    {:terminal, theme: "dracula", background: "#282a36", width: 120}

### BBCode Scoped formatter

    :bbcode_scoped

Emits highlight scope names as tags, not standard forum-style BBCode like `[b]`, `[color]`, or `[code]`.

See https://docs.rs/lumis/latest/lumis/enum.FormatterOption.html for more info.

# `header`

```elixir
@type header() :: %{close_tag: String.t(), open_tag: String.t()} | nil
```

Wraps the highlighted code with custom open and close HTML tags.

# `highlight_events_options`

```elixir
@type highlight_events_options() :: [
  annotations: [keyword()],
  rainbow_brackets: boolean(),
  budget: keyword()
]
```

Options for `highlight_events/3`.

* `:annotations` (`t:keyword/0`) - Caller-provided semantic ranges for this highlighting operation. Each is a
  keyword list with either an `:offset` or a `:position` range and optional
  `:data`:

      annotations: [
        [offset: {12, 23}, data: %{change: :added}],
        [position: {{1, 10}, {1, 21}}, data: %{change: :removed}]
      ]

  A formatter receives each opening event as
  `{:annotation_start, %{range: {start, end}, data: data}}`, with the range
  resolved to byte offsets. An empty range is a point.

  The default value is `[]`.

* `:rainbow_brackets` (`t:boolean/0`) - Render nested brackets with rainbow bracket decorations. The default value is `false`.

* `:budget` (`t:keyword/0`) - The work one render is allowed to do:

      budget: [time_limit: 1_000, match_limit: 16_384]

  Both dimensions bound the same render, so they are one option. `nil`
  selects the default for either key.

  The default value is `[]`.

  * `:time_limit` - How long one render may take, in milliseconds, `0` for no bound, or
    `nil` for the default of 5000.

    A render that runs out returns the whole file as plain text rather
    than an error, and HTML formatters mark it `data-lumis-budget="time"`.
    Loading a language is not counted against it.

    The default value is `nil`.

  * `:match_limit` - Bound on the query matches Tree-sitter keeps in progress at once, for
    the highlight and bracket queries alike, or `nil` for the default.

    Tree-sitter walks its whole pool of in-progress matches before it
    emits each capture, so the bound is what keeps highlighting linear on
    documents whose markup nests deeply enough to keep many matches open
    at once. Raising it recovers matches that would otherwise be dropped
    on such documents, at that cost.

    The default value is `nil`.

# `html_attrs`

```elixir
@type html_attrs() :: keyword(String.t() | boolean())
```

HTML attributes represented as an ordered keyword list.

A string value writes `name="value"`. `true` writes the bare `name` HTML gives
boolean attributes such as `hidden`, and `false` removes an attribute Lumis
would otherwise generate.

# `html_inline_highlight_lines`

```elixir
@type html_inline_highlight_lines() ::
  %{
    lines: [pos_integer() | Range.t()],
    style: :theme | String.t() | nil,
    class: String.t() | nil
  }
  | nil
```

Highlight lines options for Inline HTML formatter.

# `html_linked_highlight_lines`

```elixir
@type html_linked_highlight_lines() ::
  %{lines: [pos_integer() | Range.t()], class: String.t()} | nil
```

Highlight lines options for Linked HTML formatter.

# `html_multi_themes_options`

```elixir
@type html_multi_themes_options() ::
  %{
    structure: html_structure(),
    themes: keyword(theme()),
    default_theme: String.t() | nil,
    css_variable_prefix: String.t() | nil,
    pre_class: String.t() | nil,
    pre_attrs: html_attrs(),
    code_attrs: html_attrs(),
    italic: boolean(),
    include_highlights: boolean(),
    highlight_lines: html_inline_highlight_lines() | nil,
    line_numbers: boolean(),
    header: header()
  }
  | nil
```

Options for HTML Multi-Themes formatter.

The themes are specified as a keyword list where keys are CSS identifiers (atoms)
and values are theme names (strings) or Theme structs.

# `html_structure`

```elixir
@type html_structure() :: :block | :inline
```

What an HTML formatter writes around the highlighted tokens.

`:block` is a `<pre><code>` block holding one `<span class="l-line">` per line.
`:inline` is the token spans and the text between them, and nothing else, for
a `<code>` or other element the page already owns. Lines are separated by
`\n` and the last one has no terminator, as in a block. `:pre_class`,
`:pre_attrs`, `:code_attrs`, `:highlight_lines`, `:line_numbers` and `:header`
have no effect. The theme's text and background color are not written either,
because a block writes them on its `<pre>`; put
`Lumis.Formatter.HTML.pre_attrs/1` on your own element to keep them.

# `language`

```elixir
@type language() :: String.t() | nil
```

A language name, filename, or path with extension.

See `Lumis.available_languages/0` to list all available languages or check out a list of [available languages](https://docs.rs/lumis/latest/lumis/#languages-available).

## Examples

    - "elixir"
    - ".ex"
    - "app.ex"
    - "lib/app.ex"

# `language_info`

```elixir
@type language_info() :: %{
  id: String.t(),
  name: String.t(),
  aliases: [String.t()],
  extensions: [String.t()],
  globs: [String.t()],
  emacs_modes: [String.t()],
  shebangs: [String.t()]
}
```

What Lumis knows about one language.

# `options`

```elixir
@type options() :: [
  language: language(),
  formatter: formatter(),
  theme: struct() | binary() | nil,
  inline_style: boolean(),
  pre_class: binary() | nil,
  annotations: [keyword()],
  rainbow_brackets: boolean(),
  budget: keyword()
]
```

* `:language` (`t:Lumis.language/0`) - *This option is deprecated. Use the :language option inside the formatter tuple instead, eg: {:html_inline, language: "elixir"}*

* `:formatter` (`t:Lumis.formatter/0`) - Formatter to apply on the highlighted source code. See the type doc for more info. The default value is `{:html_inline, []}`.

* `:theme` - *This option is deprecated. Use :formatter instead.*

* `:inline_style` (`t:boolean/0`) - *This option is deprecated. Use :formatter instead.*

* `:pre_class` - *This option is deprecated. Use :formatter instead.*

* `:annotations` (`t:keyword/0`) - Caller-provided semantic ranges for this highlighting operation. Each is a
  keyword list with either an `:offset` or a `:position` range and optional
  `:data`:

      annotations: [
        [offset: {12, 23}, data: %{change: :added}],
        [position: {{1, 10}, {1, 21}}, data: %{change: :removed}]
      ]

  A formatter receives each opening event as
  `{:annotation_start, %{range: {start, end}, data: data}}`, with the range
  resolved to byte offsets. An empty range is a point.

  The default value is `[]`.

* `:rainbow_brackets` (`t:boolean/0`) - Render nested brackets with rainbow bracket decorations. The default value is `false`.

* `:budget` (`t:keyword/0`) - The work one render is allowed to do:

      budget: [time_limit: 1_000, match_limit: 16_384]

  Both dimensions bound the same render, so they are one option. `nil`
  selects the default for either key.

  The default value is `[]`.

  * `:time_limit` - How long one render may take, in milliseconds, `0` for no bound, or
    `nil` for the default of 5000.

    A render that runs out returns the whole file as plain text rather
    than an error, and HTML formatters mark it `data-lumis-budget="time"`.
    Loading a language is not counted against it.

    The default value is `nil`.

  * `:match_limit` - Bound on the query matches Tree-sitter keeps in progress at once, for
    the highlight and bracket queries alike, or `nil` for the default.

    Tree-sitter walks its whole pool of in-progress matches before it
    emits each capture, so the bound is what keeps highlighting linear on
    documents whose markup nests deeply enough to keep many matches open
    at once. Raising it recovers matches that would otherwise be dropped
    on such documents, at that cost.

    The default value is `nil`.

See each option type for more info.

# `terminal_highlight_lines`

```elixir
@type terminal_highlight_lines() ::
  %{lines: [pos_integer() | Range.t()], background: String.t() | nil} | nil
```

Highlight lines options for the Terminal formatter.

# `theme`

```elixir
@type theme() :: String.t() | Lumis.Theme.t() | nil
```

Theme used to apply styles on the highlighted source code.

See `Lumis.available_themes/0` to list all available themes or check out a list of [available themes](https://docs.rs/lumis/latest/lumis/#themes-available).

# `theme_info`

```elixir
@type theme_info() :: %{name: String.t(), appearance: String.t()}
```

A built-in theme's name and appearance, without its highlight data.

# `available_languages`

```elixir
@spec available_languages() :: [language_info()]
```

Returns every available language and what the catalog knows about it, sorted
by id.

## Example

    iex> Lumis.available_languages() |> Enum.find(&(&1.id == "elixir"))
    %{
      id: "elixir",
      name: "Elixir",
      aliases: [],
      extensions: ["*.ex", "*.exs"],
      globs: ["*.ex", "*.exs"],
      emacs_modes: ["elixir"],
      shebangs: ["elixir"]
    }

# `available_themes`

```elixir
@spec available_themes() :: [theme_info()]
```

Returns every built-in theme's name and appearance, sorted by name.

Use `Lumis.Theme.get/2` to get the actual theme struct.

## Example

    iex> Lumis.available_themes() |> Enum.find(&(&1.name == "github_light"))
    %{name: "github_light", appearance: "light"}

# `default_options`

```elixir
@spec default_options() :: options()
```

Returns all default options.

# `highlight`

```elixir
@spec highlight(String.t(), options()) ::
  {:ok, String.t()} | {:error, Lumis.RenderError.t()}
```

Highlights `source` code and outputs into a formatted string.

A language whose parser is not installed as a dependency renders as plain text.

Invalid *options* still raise, because those are a caller mistake rather than
a runtime condition.

## Options

See `t:options/0`.

## Examples

Defining the language name:

    iex> Lumis.highlight("Atom.to_string(:elixir)", formatter: {:html_inline, language: "elixir"})
    {
      :ok,
      <pre class="lumis" style="color: #abb2bf; background-color: #282c34;"><code class="language-elixir" translate="no" tabindex="0"><span class="l-line" data-line="1"><span style="color: #e5c07b;">Atom</span><span style="color: #56b6c2;">.</span><span style="color: #61afef;">to_string</span><span style="color: #c678dd;">(</span><span style="color: #e06c75;">:elixir</span><span style="color: #c678dd;">)</span></span></code></pre>
    }

Guessing the language based on the provided source code:

    iex> Lumis.highlight("#!/usr/bin/env bash\nID=1")
    {:ok, "<pre class="lumis" ...><code class="language-bash" ...>...</code></pre>"}

With custom options:

    iex> Lumis.highlight("Atom.to_string(:elixir)", formatter: {:html_inline, language: "example.ex", pre_class: "example-elixir"})
    {:ok, "<pre class="lumis example-elixir" ...><code ...>...</code></pre>"}

Terminal formatter:

    iex> Lumis.highlight("Atom.to_string(:elixir)", formatter: {:terminal, language: "elixir"})
    {:ok, "[0m[38;2;229;192;123mAtom[0m[0m[38;2;86;182;194m.[0m[0m[38;2;97;175;239mto_string[0m[0m[38;2;198;120;221m([0m[0m[38;2;224;108;117m:elixir[0m[0m[38;2;198;120;221m)[0m"}

Highlighting specific lines in HTML Inline formatter:

    iex> code = """
    ...> defmodule Example do
    ...>   @lang = :elixir
    ...>   def lang, do: @lang
    ...> end
    ...> """
    iex> highlight_lines = %{lines: [2]}
    iex> Lumis.highlight(code, formatter: {:html_inline, language: "elixir", highlight_lines: highlight_lines})
    # Line 2 will be highlighted with the theme's `highlighted` style:
    <span class="l-line" style="display: inline-block; width: 100%; min-height: 1lh; vertical-align: top; background-color: #414858;" data-line="2">...</span>

Highlighting specific lines in HTML Linked formatter:
    
    iex> code = """
    ...> defmodule Example do
    ...>   @lang = :elixir
    ...>   def lang, do: @lang
    ...> end
    ...> """
    iex> highlight_lines = %{lines: [2]}
    iex> Lumis.highlight(code, formatter: {:html_linked, language: "elixir", highlight_lines: highlight_lines})
    # Line 2 will contain a `l-highlighted` class:
    <span class="l-line l-highlighted" data-line="2">...

Wrapping with custom HTML:

    iex> header = %{
    ...>   open_tag: "<figure><span>file: example.exs</span>",
    ...>   close_tag: "</figure>"
    ...> }
    iex> Lumis.highlight("IO.puts('hello')", formatter: {:html_inline, language: "elixir", header: header})
    {:ok, "<figure><span>file: example.exs</span><pre...><code ...>...</code></pre></figure>"}

See https://docs.rs/lumis/latest/lumis/fn.highlight.html for more info.

# `highlight`

> This function is deprecated. Use highlight/2 instead.

# `highlight!`

```elixir
@spec highlight!(String.t(), keyword()) :: String.t()
```

Same as `highlight/2` but raises `Lumis.HighlightError` in case of failure.

# `highlight!`

> This function is deprecated. Use highlight!/2 instead.

# `highlight_events`

```elixir
@spec highlight_events(String.t(), String.t() | nil, highlight_events_options()) ::
  {:ok, [Lumis.Formatter.event(term())]} | {:error, Lumis.RenderError.t()}
```

Highlights `source` into the event stream a custom formatter receives, without
rendering it.

Reach for it when a formatter's single string is the wrong shape for the
result, such as one HTML fragment per line:

    {:ok, events} = Lumis.highlight_events(source, "elixir")
    Lumis.Formatter.HTML.render_lines_from_events(source, events, attrs)

`language` is a language name, a file name or path, or `nil` to detect it from
`source`. A language whose parser is not installed comes back as one plain
`:source` event.

The counterpart of `highlight_events` in Rust and `highlightEvents()` in
JavaScript.

## Options

See `t:highlight_events_options/0`.

## Example

    iex> {:ok, events} = Lumis.highlight_events("x = 1", "elixir")
    iex> Enum.take(events, 3)
    [{:start, %{scope: "variable", language: "elixir"}}, {:source, %{start: 0, end: 1}}, :end]

# `highlight_events!`

```elixir
@spec highlight_events!(String.t(), String.t() | nil, highlight_events_options()) :: [
  Lumis.Formatter.event(term())
]
```

Same as `highlight_events/3` but raises `Lumis.HighlightError` in case of failure.

# `loaded_languages`

```elixir
@spec loaded_languages() :: [id :: String.t()]
```

Returns the ids of the languages loaded into this VM, sorted.

The complement of `available_languages/0`: what can be highlighted right now
without a download. Loading is global to the VM, so this is the same list in
every process.

## Example

    iex> Lumis.Languages.load("elixir")
    iex> Lumis.loaded_languages()
    ["elixir"]

# `validate_options!`

```elixir
@spec validate_options!(options()) :: options()
```

Validates the given options against the options schema.

This function validates the provided options using NimbleOptions and the defined schema.
It ensures that all options are valid and properly typed before being passed to the
highlighting functions.

## Examples

    iex> Lumis.validate_options!(formatter: {:html_inline, language: "elixir"})
    [formatter: {:html_inline, [header: nil, line_numbers: nil, highlight_lines: nil, include_highlights: false, italic: false, pre_class: nil, theme: nil, language: "elixir"]}]

    iex> Lumis.validate_options!(formatter: {:html_inline, theme: "dracula"})
    [formatter: {:html_inline, [theme: "dracula", ...]}]

    iex> Lumis.validate_options!(language: :invalid)
    ** (NimbleOptions.ValidationError)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
