# Highlight (/usage/highlight)

This is the core Lumis workflow: choose a language, choose a formatter, then render output.

## Simple example

### JavaScript / TypeScript

```js
import {highlight} from '@lumis-sh/lumis';
import {htmlInline} from '@lumis-sh/lumis/formatters';
import javascript from '@lumis-sh/wasm-javascript';
import frappe from '@lumis-sh/themes/catppuccin_frappe';

const html = await highlight(
  'const x = 1',
  htmlInline({language: javascript, theme: frappe})
);
```

### Rust

```rust
use lumis::{
    highlight, languages::Language, themes,
    HtmlInlineBuilder,
};

let theme = themes::get("catppuccin_frappe").unwrap();

let formatter = HtmlInlineBuilder::new()
    .language(Language::JavaScript)
    .theme(Some(theme))
    .build()
    .unwrap();

let html = highlight("const x = 1", formatter);
```

### Elixir

```elixir
Lumis.highlight!(
  "const x = 1",
  formatter:
    {:html_inline,
     language: "javascript", theme: "catppuccin_frappe"}
)
```

### Java

```java
import io.roastedroot.lumis4j.core.Lang;
import io.roastedroot.lumis4j.core.Lumis;
import io.roastedroot.lumis4j.core.Theme;

var lumis = Lumis.builder().build();

var highlighter = lumis.highlighter()
    .withLang(Lang.JAVASCRIPT)
    .withTheme(Theme.CATPPUCCIN_FRAPPE)
    .build();

var html = highlighter.highlight("const x = 1").string();
```

### CLI

```bash
lumis highlight index.js \
  --language javascript \
  --theme catppuccin_frappe
```


## Options by runtime

| Need | CLI | Rust | Elixir | JavaScript / TypeScript | Java |
| --- | --- | --- | --- | --- | --- |
| Set language | `--language` | `.language(...)` | `language: "..."` | `language: ...` | `withLang(...)` |
| Set theme | `--theme` | `.theme(...)` | `theme: "..."` | `theme: ...` | `withTheme(...)` |
| Choose output | `--formatter` | builder type | `formatter: ...` | formatter factory | runtime-specific |
| Bound query matches | `--budget-match-limit` | `.budget(Budget::new().match_limit(...))` | `budget: [match_limit: ...]` | `budget: {matchLimit: ...}` | — |
| Bound render time | `--budget-time-limit` | `.budget(Budget::new().time_limit(...))` | `budget: [time_limit: ...]` | `budget: {timeLimit: ...}` | — |
| Color nested brackets | `--rainbow-brackets` | `.rainbow_brackets(true)` | `rainbow_brackets: true` | `rainbowBrackets: true` | — |
| Mark changed words (`diff` language only) | `--word-diff` | `.word_diff(true)` | `word_diff: true` | `wordDiff: true` | — |
| Compose your own ranges | — | `.annotations(&annotations)` | `annotations: [...]` | `annotations: [...]` | — |

Both limits are one feature, so they sit under one `budget` option rather than
two loose ones. See [Budget](/advanced/budget) for what happens when either
runs out. A CLI has no nesting, so the group becomes a flag prefix.

`budget`, `rainbow_brackets`, `word_diff` and `annotations` belong to the highlight rather
than to a formatter: they change which events the walk produces, and every
formatter renders whatever it is handed. So they sit beside the formatter rather
than inside it — `HighlightOptions` in Rust and JavaScript, the options list of
`Lumis.highlight/2` in Elixir. See [Rainbow brackets](/recipes/rainbow-brackets),
[Highlight diffs](/recipes/highlight-diffs#mark-the-words-that-changed) and
[Annotations](/formatters/annotations).

## Language detection

If you omit the language, Lumis tries to detect it using:

- file extensions (`.rs`, `.js`, `.ex`, etc.)
- filenames (`Makefile`, `Dockerfile`, `Caddyfile`)
- shebangs (`#!/usr/bin/env python`)
- Emacs mode lines (`-*- mode: ruby -*-`)

If detection fails, it falls back to plaintext.

## Match limit

Tree-sitter walks its whole pool of in-progress matches before it emits each capture, so a query pattern that stays open across a large subtree costs more the deeper the document nests. Lumis bounds the number of matches kept in progress at 8192, for the highlight and bracket queries alike. HTML and the languages that embed it do not depend on that bound to stay fast, because their layers replay captures from finished matches; the bound is what keeps other languages linear when a query pattern stays open across a large subtree.

Raise it when a document is large enough that tree-sitter drops matches and scopes go missing, and accept the extra time. Tree-sitter accepts 1 to 65536; every runtime rejects anything else.

When matches are dropped, HTML formatters mark the `<pre>` with `data-lumis-budget="matches"` so the missing scopes are detectable rather than silent. See [Budget](/advanced/budget).

### JavaScript / TypeScript

```js
const html = await highlight(
  source,
  htmlInline({language: html_, theme: frappe}),
  {budget: {matchLimit: 16384}},
);
```

### Rust

```rust
use lumis::highlight::{Budget, HighlightOptions};

let options = HighlightOptions::new().budget(Budget::new().match_limit(16_384));
```

### Elixir

```elixir
Lumis.highlight(source, budget: [match_limit: 16_384])
```

### CLI

```bash
lumis highlight index.html --budget-match-limit 16384
```


## Incomplete code

Lumis handles incomplete or malformed code without crashing. Tree-sitter produces a partial syntax tree and Lumis highlights what it can. This is useful for streaming scenarios where code arrives in chunks (e.g., LLM output).

## Next steps

- choose an output mode in [Formatters](/formatters)
- pick or generate colors in [Themes](/themes)
- emphasize lines in [Line Highlighting](/recipes/line-highlighting)
- number lines in [Line Numbers](/recipes/line-numbers)