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

The ANSI pieces the built-in `:terminal` formatter is assembled from.

A module implementing `Lumis.Formatter` gets an event stream and has to turn
it into output; these helpers apply the same colors, text decorations, and
reset behavior as the built-in formatter.

## Example

    defmodule MyTerminalFormatter do
      @behaviour Lumis.Formatter

      alias Lumis.Formatter.ANSI

      @impl true
      def render(source, events, options) do
        theme = Keyword.get(options, :theme)

        {output, _scopes, _tables} =
          Enum.reduce(events, {[], [], %{}}, fn
            {:start, %{scope: scope, language: language}}, {output, scopes, tables} ->
              {output, [{scope, language} | scopes], tables}

            :end, {output, [_scope | scopes], tables} ->
              {output, scopes, tables}

            {:source, %{start: start, end: stop}}, {output, scopes, tables} ->
              text = binary_part(source, start, stop - start)

              case scopes do
                [] ->
                  {[text | output], scopes, tables}

                [{scope, language} | _rest] ->
                  # One table per language, built once and read per token.
                  tables =
                    Map.put_new_lazy(tables, language, fn ->
                      ANSI.styles(theme: theme, language: language)
                    end)

                  painted = ANSI.paint(text, ANSI.style_for(tables[language], scope))
                  {[painted | output], scopes, tables}
              end

            # Lumis adds event kinds as it grows. Render the ones you know and
            # skip the rest, or a newer Lumis raises FunctionClauseError here.
            _event, state ->
              state
          end)

        Enum.reverse(output)
      end
    end

    Lumis.highlight!("defmodule App do\nend",
      formatter: {MyTerminalFormatter, language: "elixir", theme: "dracula"}
    )

# `hex_to_rgb`

```elixir
@spec hex_to_rgb(String.t()) :: {0..255, 0..255, 0..255} | nil
```

Converts a six-digit hex color to an RGB tuple.

A leading `#` is optional. Returns `nil` when the color is not six hexadecimal
digits.

    iex> Lumis.Formatter.ANSI.hex_to_rgb("#ff79c6")
    {255, 121, 198}

    iex> Lumis.Formatter.ANSI.hex_to_rgb("fff")
    nil

# `paint`

```elixir
@spec paint(String.t(), Lumis.Theme.Style.t() | nil) :: String.t()
```

Paints `text` with a `Lumis.Theme.Style`.

The result uses the same reset and newline handling as the built-in
`:terminal` formatter. Text with an empty style, or with the `nil` that
`style_for/2` returns for an unstyled scope, is returned unchanged.

# `reset`

```elixir
@spec reset() :: String.t()
```

Returns the ANSI sequence that clears all formatting.

# `rgb_to_ansi`

```elixir
@spec rgb_to_ansi(0..255, 0..255, 0..255, boolean()) :: String.t()
```

Builds a 24-bit ANSI color escape sequence from RGB components.

Set `is_background` to `true` for a background color and `false` for a
foreground color.

# `style_for`

```elixir
@spec style_for(%{required(String.t()) =&gt; Lumis.Theme.Style.t()}, String.t() | nil) ::
  Lumis.Theme.Style.t() | nil
```

A scope's style, read out of a `styles/1` table.

Returns `nil` for a scope the theme styles in no way, which `paint/2` renders
unchanged.

# `style_to_ansi`

```elixir
@spec style_to_ansi(Lumis.Theme.Style.t()) :: String.t()
```

Converts a `Lumis.Theme.Style` to ANSI escape sequences.

Covers foreground and background colors, bold, italic, strikethrough, and
every underline style supported by `Lumis.Theme.TextDecoration`.

# `styles`

```elixir
@spec styles(keyword()) :: %{required(String.t()) =&gt; Lumis.Theme.Style.t()}
```

Every scope's style for one theme and language.

Resolving a scope is a per-token operation, so this is the whole table: build
it once outside the loop and read it inside with `style_for/2`. A scope the
theme styles in no way is absent from it.

A scope resolves the way `:terminal` resolves it, which is not a lookup in
`theme.highlights` — `tag.delimiter` falls back to `tag`, and a theme can
style a scope per language. An injected block carries its own language on its
`:start` event, so a document with injections needs one table per language.

## Options

  * `:theme` (`t:Lumis.Theme.t/0` or a theme name) — the theme to resolve
    against. Without one the table is empty, which paints nothing.
  * `:language` — the language whose specialized scopes to prefer, e.g.
    `comment.elixir` over `comment`. Defaults to `"plaintext"`.

---

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