# `Drafter.Terminal.ANSI`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/terminal/ansi.ex#L1)

Decodes terminal input bytes into events and builds terminal output sequences.

## Input

`parse_sequence/1` turns a byte buffer into a list of events plus the bytes that
cannot yet be decided. `flush_sequence/1` does the same but resolves the trailing
ambiguity instead of retaining it. `incomplete_sequence?/1` reports whether a
buffer ends mid-sequence.

    iex> Drafter.Terminal.ANSI.parse_sequence("hi\e[A\e[")
    {[{:key, :h}, {:key, :i}, {:key, :up}], "\e["}

## Event shapes

Every event this module produces takes one of these forms:

  * `{:key, key}` — a named key (`:up`, `:enter`, `:f5`, `:backspace`, `:escape`)
    or, for printable ASCII 32..126, the character itself as an atom (`:a`, `:Z`,
    `:"1"`, `:" "`).
  * `{:key, key, modifiers}` — the same, with a non-empty modifier list. Modifiers
    are a subset of `[:ctrl, :alt, :shift]` and appear in that order.
  * `{:char, codepoint}` — a printable codepoint outside ASCII 32..126, as an
    integer.
  * `{:mouse, payload}` — see below.
  * `{:bracketed_paste, text}` — the content between `ESC [ 200~` and `ESC [ 201~`,
    undecoded and with the delimiters stripped.
  * `{:key_down, key, modifiers}`, `{:key_up, key, modifiers}` and
    `{:key_release_support, boolean}` — reports in the kitty keyboard protocol,
    which a terminal sends only after a driver has turned it on; the support event
    is `true` only when the terminal will report releases. See
    `Drafter.Terminal.KittyKeyboard`.
  * `{:cell_size, {width, height}}` — the terminal's answer, in pixels, to
    `Drafter.Terminal.Reports.cell_size_query/0`.

Control characters `\x01`..`\x1a` decode as `{:key, letter, [:ctrl]}`, except
`\x09` which is `{:key, :tab}` and `\x0a`/`\x0d` which are both `{:key, :enter}`.

## Mouse payloads

`x` and `y` are zero-based column and row. `modifiers` is a subset of
`[:ctrl, :alt, :shift]`.

  * `%{type: :mouse_down | :mouse_up | :drag, button: button, x: x, y: y, modifiers: mods}`
    where `button` is `:left`, `:middle`, `:right`, `:scroll` or `:unknown`
  * `%{type: :move, x: x, y: y, modifiers: mods}`
  * `%{type: :scroll, direction: :up | :down | :left | :right, x: x, y: y, modifiers: mods}`

SGR (`ESC [ < …M/m`), legacy numeric and X10 (`ESC [ M` plus three bytes) encodings
are all accepted. A final `H` in the SGR form is read as if it were `M`.

## Discarded input

Terminals reply to queries with string-typed control sequences opened by
`ESC P`, `ESC ]`, `ESC ^`, `ESC _` or `ESC X` and closed by a string terminator
(`ESC \`, or `BEL` for OSC). These are consumed and produce no event. A buffer
ending inside one is treated as incomplete.

A complete CSI sequence (`ESC [`, parameter and intermediate bytes `0x20`..`0x3F`,
a final byte `0x40`..`0x7E`) that no key, mouse or report parser reads is consumed
the same way. `ESC [` with no final byte yet is incomplete; on flush it is the
escape key followed by `[`.

The release report for a scroll button — buttons `64`..`67` with a final `m` —
is consumed the same way, since the press already carried the scroll.

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[<64;1;1m")
    {[], ""}

## Output

The remaining functions return the escape sequences for cursor placement, screen
clearing, alternate screen, synchronized updates, mouse reporting and SGR styling.
They only build strings; writing them is the caller's job.

    iex> Drafter.Terminal.ANSI.cursor_to(1, 1) <> Drafter.Terminal.ANSI.clear_screen()
    "\e[1;1H\e[2J"

# `event`

```elixir
@type event() ::
  {:key, key()}
  | {:key, key(), modifiers()}
  | {:char, char()}
  | {:mouse, mouse_payload()}
  | {:bracketed_paste, binary()}
  | {:key_down, KittyKeyboard.key(), modifiers()}
  | {:key_up, KittyKeyboard.key(), modifiers()}
  | {:key_release_support, boolean()}
  | {:cell_size, {pos_integer(), pos_integer()}}
```

Everything `parse_sequence/1` and `flush_sequence/1` can produce.

# `key`

```elixir
@type key() :: atom()
```

A named key such as `:up` or `:f5`, or a printable ASCII character as an atom.

# `modifiers`

```elixir
@type modifiers() :: [:ctrl | :alt | :shift]
```

Held modifiers, in the order `:ctrl`, `:alt`, `:shift`, and never empty in an event.

# `mouse_payload`

```elixir
@type mouse_payload() :: %{
  :type =&gt; :mouse_down | :mouse_up | :drag | :move | :scroll,
  :x =&gt; non_neg_integer(),
  :y =&gt; non_neg_integer(),
  :modifiers =&gt; modifiers(),
  optional(:button) =&gt; :left | :middle | :right | :scroll | :unknown,
  optional(:direction) =&gt; :up | :down | :left | :right
}
```

What a `{:mouse, payload}` event carries.

`:button` is absent on `:move`, and `:direction` is present only on `:scroll`.

# `bg_color`

```elixir
@spec bg_color(non_neg_integer(), non_neg_integer(), non_neg_integer()) :: String.t()
```

Set the background to the 24-bit color `r`, `g`, `b`, each `0..255`.

    iex> Drafter.Terminal.ANSI.bg_color(0, 0, 128)
    "\e[48;2;0;0;128m"

# `bold`

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

Turn on bold.

    iex> Drafter.Terminal.ANSI.bold()
    "\e[1m"

# `clear_line`

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

Clear the whole line the cursor is on.

    iex> Drafter.Terminal.ANSI.clear_line()
    "\e[2K"

# `clear_screen`

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

Clear the entire screen, leaving the cursor where it is.

    iex> Drafter.Terminal.ANSI.clear_screen()
    "\e[2J"

# `clear_to_end`

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

Clear from the cursor to the end of the screen.

    iex> Drafter.Terminal.ANSI.clear_to_end()
    "\e[0J"

# `cursor_to`

```elixir
@spec cursor_to(non_neg_integer(), non_neg_integer()) :: String.t()
```

Place the cursor at column `x`, row `y`, both counted from `1`.

The arguments are in column-then-row order, the reverse of the order they take
in the sequence itself.

    iex> Drafter.Terminal.ANSI.cursor_to(3, 5)
    "\e[5;3H"

# `dim`

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

Turn on dim.

    iex> Drafter.Terminal.ANSI.dim()
    "\e[2m"

# `disable_mouse`

```elixir
@spec disable_mouse(keyword()) :: String.t()
```

Sequence that turns off mouse reporting.

Pass the same `:hover` value that was given to `enable_mouse/1`, so the mode that
was set is the mode that is cleared. Defaults to `true`.

    iex> Drafter.Terminal.ANSI.disable_mouse()
    "\e[?1006l\e[?1003l"

    iex> Drafter.Terminal.ANSI.disable_mouse(hover: false)
    "\e[?1006l\e[?1002l"

# `enable_mouse`

```elixir
@spec enable_mouse(keyword()) :: String.t()
```

Sequence that turns on mouse reporting in SGR encoding.

Options:

  * `:hover` — when `true` (the default) any-motion tracking is enabled, so
    `:move` events arrive with no button held. When `false` only button presses,
    releases and drags are reported.

## Examples

    iex> Drafter.Terminal.ANSI.enable_mouse()
    "\e[?1003h\e[?1006h"

    iex> Drafter.Terminal.ANSI.enable_mouse(hover: false)
    "\e[?1002h\e[?1006h"

# `enter_alt_screen`

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

Switch to the alternate screen buffer, keeping the scrollback of the main one.

    iex> Drafter.Terminal.ANSI.enter_alt_screen()
    "\e[?1049h"

# `exit_alt_screen`

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

Return to the main screen buffer, restoring what was on it.

    iex> Drafter.Terminal.ANSI.exit_alt_screen()
    "\e[?1049l"

# `fg_color`

```elixir
@spec fg_color(non_neg_integer(), non_neg_integer(), non_neg_integer()) :: String.t()
```

Set the foreground to the 24-bit color `r`, `g`, `b`, each `0..255`.

    iex> Drafter.Terminal.ANSI.fg_color(255, 0, 0)
    "\e[38;2;255;0;0m"

# `flush_sequence`

```elixir
@spec flush_sequence(
  binary(),
  keyword()
) :: {[event()], binary()}
```

Parse an input buffer, resolving any trailing ambiguity instead of retaining it.

Call this when no further bytes are expected — after an escape timeout, or when
the input stream closes. A lone `ESC` becomes the escape key, and an
unterminated bracketed paste is delivered with the content received so far.

    iex> Drafter.Terminal.ANSI.flush_sequence("\e")
    {[{:key, :escape}], ""}

    iex> Drafter.Terminal.ANSI.flush_sequence("\e[200~half")
    {[{:bracketed_paste, "half"}], ""}

A codepoint split across a read is the one thing still retained, since its
remaining bytes carry no ambiguity to resolve:

    iex> Drafter.Terminal.ANSI.flush_sequence(<<0xC3>>)
    {[], <<0xC3>>}

Takes the same `:key_release` option as `parse_sequence/2`.

# `hide_cursor`

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

Hide the cursor.

    iex> Drafter.Terminal.ANSI.hide_cursor()
    "\e[?25l"

# `incomplete_sequence?`

```elixir
@spec incomplete_sequence?(binary()) :: boolean()
```

Whether the buffer ends in a control sequence that has not fully arrived.

A lone `ESC` counts as incomplete: it is indistinguishable from the start of a
sequence still in flight, so the caller must resolve it on a timeout via
`flush_sequence/1`.

    iex> Drafter.Terminal.ANSI.incomplete_sequence?("\e")
    true

    iex> Drafter.Terminal.ANSI.incomplete_sequence?("\e[1;5")
    true

    iex> Drafter.Terminal.ANSI.incomplete_sequence?("\e[A")
    false

    iex> Drafter.Terminal.ANSI.incomplete_sequence?("ab")
    false

An unterminated bracketed paste is not reported here: `ESC [ 200~` is a complete
CSI sequence. `parse_sequence/1` holds the paste back on its own.

    iex> Drafter.Terminal.ANSI.incomplete_sequence?("\e[200~half")
    false

# `italic`

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

Turn on italic.

    iex> Drafter.Terminal.ANSI.italic()
    "\e[3m"

# `parse_sequence`

```elixir
@spec parse_sequence(
  binary(),
  keyword()
) :: {[event()], binary()}
```

Parse an input buffer into events plus the bytes that could not yet be decided.

A trailing sequence that is only partially received — an unterminated
bracketed paste, a CSI awaiting its final byte, a split UTF-8 codepoint — is
returned in the remaining buffer. Callers must carry that remainder into the
next call.

Printable ASCII arrives as `{:key, atom}`, and anything else printable as
`{:char, codepoint}`:

    iex> Drafter.Terminal.ANSI.parse_sequence("A ")
    {[{:key, :A}, {:key, :" "}], ""}

    iex> Drafter.Terminal.ANSI.parse_sequence("é")
    {[{:char, 233}], ""}

Named keys, control characters and modified keys:

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[A")
    {[{:key, :up}], ""}

    iex> Drafter.Terminal.ANSI.parse_sequence("\x03")
    {[{:key, :c, [:ctrl]}], ""}

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[1;5C")
    {[{:key, :right, [:ctrl]}], ""}

Mouse reports, in any of the three encodings, become a `{:mouse, payload}` pair
with zero-based coordinates:

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[<0;10;5M")
    {[{:mouse, %{type: :mouse_down, button: :left, x: 9, y: 4, modifiers: []}}], ""}

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[<64;1;1M")
    {[{:mouse, %{type: :scroll, direction: :up, x: 0, y: 0, modifiers: []}}], ""}

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[<35;1;1M")
    {[{:mouse, %{type: :move, x: 0, y: 0, modifiers: []}}], ""}

A bracketed paste is delivered whole, with its delimiters stripped:

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[200~two words\e[201~")
    {[{:bracketed_paste, "two words"}], ""}

Replies to terminal queries are consumed without producing an event:

    iex> Drafter.Terminal.ANSI.parse_sequence("\eP1$r0m\e\\x")
    {[{:key, :x}], ""}

Whatever cannot yet be decided is handed back for the next call — an unterminated
paste, a CSI without its final byte, and a split UTF-8 codepoint alike:

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[200~half")
    {[], "\e[200~half"}

    iex> Drafter.Terminal.ANSI.parse_sequence("x\e[")
    {[{:key, :x}], "\e["}

    iex> Drafter.Terminal.ANSI.parse_sequence(<<0xC3>>)
    {[], <<0xC3>>}

Reports in the kitty keyboard protocol are read whatever the options, producing
the events `Drafter.Terminal.KittyKeyboard` describes. With `key_release: true`,
which a driver passes once it has turned that protocol on, every key press in a
legacy encoding is also followed by its `{:key_down, key, modifiers}`:

    iex> Drafter.Terminal.ANSI.parse_sequence("\e[A", key_release: true)
    {[{:key, :up}, {:key_down, :up, []}], ""}

## Options

  * `:key_release` - `boolean()`, default `false`.

# `printable_key`

```elixir
@spec printable_key(32..126) :: key()
```

The key atom for a printable ASCII codepoint, as `{:key, atom}` events carry it.

    iex> Drafter.Terminal.ANSI.printable_key(?a)
    :a

    iex> Drafter.Terminal.ANSI.printable_key(?\s)
    :" "

# `reset`

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

Clear every attribute and both colors.

    iex> Drafter.Terminal.ANSI.reset()
    "\e[0m"

# `reverse`

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

Turn on reverse video, swapping foreground and background.

    iex> Drafter.Terminal.ANSI.reverse()
    "\e[7m"

# `show_cursor`

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

Show the cursor.

    iex> Drafter.Terminal.ANSI.show_cursor()
    "\e[?25h"

# `sync_end`

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

Close the synchronized update opened by `sync_start/0` and present the frame.

    iex> Drafter.Terminal.ANSI.sync_end()
    "\e[?2026l"

# `sync_start`

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

Open a synchronized update, so the terminal shows nothing until `sync_end/0`.

Terminals that do not implement mode 2026 ignore it and draw as bytes arrive.

    iex> Drafter.Terminal.ANSI.sync_start()
    "\e[?2026h"

# `underline`

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

Turn on underline.

    iex> Drafter.Terminal.ANSI.underline()
    "\e[4m"

---

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