# Line Numbers (/recipes/line-numbers)

`line_numbers` numbers the lines a formatter renders. The three HTML formatters
and `terminal` take it; `bbcode_scoped` has nothing to render a number into and
does not.

### 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(
  'line 1\nline 2\nline 3',
  htmlInline({
    language: javascript,
    theme: frappe,
    lineNumbers: true,
  })
);
```

### Rust

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

let formatter = HtmlInlineBuilder::new()
    .language(Language::PlainText)
    .theme(Some(themes::get("catppuccin_frappe").unwrap()))
    .line_numbers(true)
    .build()
    .unwrap();

let html = highlight("line 1\nline 2\nline 3", formatter);
```

### Elixir

```elixir
Lumis.highlight!(
  "line 1\nline 2\nline 3",
  formatter:
    {:html_inline,
     language: "plaintext",
     theme: "catppuccin_frappe",
     line_numbers: true}
)
```

### Java

Line numbers are not currently exposed by `lumis4j`.

### CLI

```bash
lumis highlight main.rs \
  --formatter html-inline --line-numbers
```


Line numbers correspond to HTML's `data-line` attribute and the numbers
accepted by `highlight_lines`.

## What each formatter renders

| Formatter | Gutter |
| --- | --- |
| HTML Inline | `<span class="l-line-number">` inside the line's `<span>` |
| HTML Linked | the same |
| HTML Multi-Themes | the same |
| Terminal | the number, right-aligned to the widest one and followed by a space |
| BBCode Scoped | not available |

### HTML

The gutter is the first child of the line, inside `<code>`:

```html
<span class="l-line" data-line="1"><span class="l-line-number" style="-webkit-user-select: none; user-select: none; color: #51576d;" aria-hidden="true">1</span><span style="color: #ca9ee6;">fn</span></span>
```

It is a real element rather than `content: attr(data-line)` so the number is in
the document a reader can inspect, and `aria-hidden` so a screen reader is not
read a number before every line. Its appearance comes from the theme's `LineNr`
style. On a highlighted line it also has the `l-line-number-highlighted` class
and uses `CursorLineNr`, falling back to `LineNr` when that style is absent.

HTML Inline writes the style directly, HTML Multi-Themes writes the corresponding
theme variables, and HTML Linked uses the `.l-line-number` and
`.l-line-number-highlighted` rules in generated theme CSS. All three keep the
gutter out of selection copy with `user-select: none` (and its WebKit prefix).
Theme CSS does not choose a gutter width, so give it a column:

```css
.l-line-number {
  display: inline-block;
  width: 3ch;
  margin-right: 1ch;
  text-align: right;
}
```

Those are layout properties, which theme CSS does not set, so they apply as
written. Recoloring the gutter is different. Theme CSS writes
`.l-line-number:not(.l-line-number-highlighted)` to keep `LineNr` off a
`CursorLineNr` gutter, and two classes beat the one in a bare `.l-line-number`,
which therefore loses however late it is loaded. Scope the rule under the block
instead, and load it after the theme stylesheet:

```css
.lumis .l-line-number {
  color: #888;
}
```

That is two classes as well, so it wins on order rather than specificity, and it
recolors the highlighted gutter along with the rest. To recolor only the lines
that are not highlighted, and to win regardless of order, add the same `:not()`
the theme rule carries.

With `line_numbers` disabled, HTML contains no gutter elements. Each line has a
`data-line` attribute, which you can use to display numbers with CSS:

```css
.l-line::before { content: attr(data-line); }
```

`innerText` and `textContent` include the gutter's text; `user-select` affects
selection only. For a copy button, use the original source rather than
either property. That also preserves CRLF and final newlines exactly. Chromium
may omit trailing blank rows from selection copy when they contain only gutters.

### Terminal

The number is padded to the widest one the render will show, so the code stays
aligned past line 9.

Neovim draws the number column with `LineNr`, and with `CursorLineNr` on the
cursor line, so `CursorLine` never reaches it. Lumis follows that: a highlighted
line's background starts at its text, not at its gutter. The gutter uses every
text attribute in the corresponding theme style, including foreground, bold,
italic, and decorations. When a theme has no `CursorLineNr` style, a highlighted
gutter falls back to its `LineNr` style.

A final newline terminates the last line; it does not add an extra line number
in either terminal or HTML output.

```sh
lumis highlight -f terminal -t catppuccin_frappe -n main.rs
```

```text
 8 fn main() {
 9     println!("hello");
10 }
```