# `ExRatatui`
[🔗](https://github.com/mcass19/ex_ratatui/blob/v0.7.1/lib/ex_ratatui.ex#L1)

Elixir bindings for the Rust [ratatui](https://ratatui.rs) terminal UI library.

This module provides the core API for building terminal UIs: initializing
the terminal, drawing widgets, and polling for events — all via Rust NIFs
that run on the BEAM's DirtyIo scheduler.

## Quick start

    ExRatatui.run(fn terminal ->
      {w, h} = ExRatatui.terminal_size()
      paragraph = %ExRatatui.Widgets.Paragraph{text: "Hello!"}
      rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: w, height: h}

      ExRatatui.draw(terminal, [{paragraph, rect}])
      ExRatatui.poll_event(60_000)
    end)

## Core functions

  * `run/1` — initialize the terminal, run a function, restore on exit
  * `draw/2` — render a list of `{widget, rect}` tuples in a single frame
  * `poll_event/1` — non-blocking event polling (keyboard, mouse, resize)
  * `terminal_size/0` — current terminal dimensions

## OTP apps

For supervised TUI applications, see `ExRatatui.App` — a behaviour with
LiveView-inspired callbacks (`mount/1`, `render/2`, `handle_event/2`).

## Running over SSH

To serve a TUI to remote clients without a local terminal, see
`ExRatatui.SSH.Daemon` and the [SSH transport guide](guides/ssh_transport.md).

## Widgets

See `ExRatatui.Widgets.Paragraph`, `ExRatatui.Widgets.Block`,
`ExRatatui.Widgets.List`, `ExRatatui.Widgets.Table`,
`ExRatatui.Widgets.Gauge`, `ExRatatui.Widgets.LineGauge`,
`ExRatatui.Widgets.Tabs`, `ExRatatui.Widgets.Scrollbar`,
`ExRatatui.Widgets.Checkbox`, `ExRatatui.Widgets.TextInput`,
`ExRatatui.Widgets.Clear`, `ExRatatui.Widgets.Markdown`,
`ExRatatui.Widgets.Textarea`, `ExRatatui.Widgets.Throbber`,
`ExRatatui.Widgets.Popup`, and `ExRatatui.Widgets.WidgetList`.

## Testing

Use `init_test_terminal/2` and `get_buffer_content/1` for headless
rendering verification in CI — no TTY required.

# `terminal_ref`

```elixir
@type terminal_ref() :: reference()
```

# `widget`

```elixir
@type widget() ::
  ExRatatui.Widgets.Paragraph.t()
  | ExRatatui.Widgets.Block.t()
  | ExRatatui.Widgets.Checkbox.t()
  | ExRatatui.Widgets.Clear.t()
  | ExRatatui.Widgets.List.t()
  | ExRatatui.Widgets.Table.t()
  | ExRatatui.Widgets.Gauge.t()
  | ExRatatui.Widgets.LineGauge.t()
  | ExRatatui.Widgets.Tabs.t()
  | ExRatatui.Widgets.Scrollbar.t()
  | ExRatatui.Widgets.Markdown.t()
  | ExRatatui.Widgets.Popup.t()
  | ExRatatui.Widgets.Textarea.t()
  | ExRatatui.Widgets.TextInput.t()
  | ExRatatui.Widgets.Throbber.t()
  | ExRatatui.Widgets.WidgetList.t()
```

# `draw`

```elixir
@spec draw(terminal_ref(), [{widget(), ExRatatui.Layout.Rect.t()}]) ::
  :ok | {:error, term()}
```

Draws a list of `{widget, rect}` tuples to the terminal in a single frame.

Each tuple pairs a widget struct (e.g. `%Paragraph{}`, `%Table{}`) with a
`%Rect{}` that defines where to render it. Returns `:ok` on success or
`{:error, reason}` on failure.

## Examples

    iex> terminal = ExRatatui.init_test_terminal(40, 10)
    iex> paragraph = %ExRatatui.Widgets.Paragraph{text: "Hello!"}
    iex> rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: 40, height: 10}
    iex> ExRatatui.draw(terminal, [{paragraph, rect}])
    :ok

# `get_buffer_content`

```elixir
@spec get_buffer_content(terminal_ref()) :: String.t() | {:error, term()}
```

Returns the test terminal's buffer contents as a string.

Each line is trimmed of trailing whitespace and joined with newlines.
Only works with a test terminal reference from `init_test_terminal/2`.

## Examples

    iex> terminal = ExRatatui.init_test_terminal(20, 3)
    iex> paragraph = %ExRatatui.Widgets.Paragraph{text: "Hi there"}
    iex> rect = %ExRatatui.Layout.Rect{x: 0, y: 0, width: 20, height: 3}
    iex> ExRatatui.draw(terminal, [{paragraph, rect}])
    iex> ExRatatui.get_buffer_content(terminal) =~ "Hi there"
    true

# `init_test_terminal`

```elixir
@spec init_test_terminal(non_neg_integer(), non_neg_integer()) ::
  terminal_ref() | {:error, term()}
```

Initializes a headless test terminal with the given dimensions.

Takes `width` (columns) and `height` (rows) for the virtual terminal.
Uses ratatui's TestBackend — no real terminal needed. Useful for testing
rendering output without a TTY. Returns a terminal reference.

## Examples

    iex> terminal = ExRatatui.init_test_terminal(40, 10)
    iex> is_reference(terminal)
    true

    iex> terminal = ExRatatui.init_test_terminal(40, 10)
    iex> alias ExRatatui.Widgets.Paragraph
    iex> alias ExRatatui.Layout.Rect
    iex> :ok = ExRatatui.draw(terminal, [{%Paragraph{text: "Hello!"}, %Rect{x: 0, y: 0, width: 40, height: 10}}])
    iex> ExRatatui.get_buffer_content(terminal) =~ "Hello!"
    true

# `poll_event`

```elixir
@spec poll_event(non_neg_integer()) :: ExRatatui.Event.t() | nil | {:error, term()}
```

Polls for terminal events with a timeout (default 250ms).

Returns an `Event.Key`, `Event.Mouse`, `Event.Resize` struct, `nil`
if no event within the timeout, or `{:error, reason}` on failure.

# `run`

```elixir
@spec run((terminal_ref() -&gt; term())) :: term() | {:error, term()}
```

Runs a TUI application.

Initializes the terminal, calls `fun` with the terminal reference,
and ensures terminal cleanup on exit.

    ExRatatui.run(fn terminal ->
      # your TUI loop here
    end)

# `terminal_size`

```elixir
@spec terminal_size() :: {non_neg_integer(), non_neg_integer()} | {:error, term()}
```

Returns the current terminal size as `{width, height}`.

Returns `{:error, reason}` if the terminal size cannot be determined.

# `text_input_cursor`

```elixir
@spec text_input_cursor(reference()) :: non_neg_integer()
```

Returns the current cursor position from the TextInput state.

## Examples

    iex> state = ExRatatui.text_input_new()
    iex> ExRatatui.text_input_cursor(state)
    0
    iex> ExRatatui.text_input_handle_key(state, "a")
    :ok
    iex> ExRatatui.text_input_cursor(state)
    1

# `text_input_get_value`

```elixir
@spec text_input_get_value(reference()) :: String.t()
```

Returns the current text value from the TextInput state.

## Examples

    iex> state = ExRatatui.text_input_new()
    iex> ExRatatui.text_input_get_value(state)
    ""

# `text_input_handle_key`

```elixir
@spec text_input_handle_key(reference(), String.t()) :: :ok
```

Forwards a key event to the TextInput state.

Pass the `code` field from an `ExRatatui.Event.Key` struct. Supports
printable characters, `"backspace"`, `"delete"`, `"left"`, `"right"`,
`"home"`, and `"end"`.

## Examples

    iex> state = ExRatatui.text_input_new()
    iex> ExRatatui.text_input_handle_key(state, "h")
    :ok
    iex> ExRatatui.text_input_handle_key(state, "i")
    :ok
    iex> ExRatatui.text_input_get_value(state)
    "hi"

# `text_input_new`

```elixir
@spec text_input_new() :: reference()
```

Creates a new TextInput state.

Returns a reference to the Rust-side state (ResourceArc). Pass this
reference as the `:state` field of `%ExRatatui.Widgets.TextInput{}`.

## Examples

    iex> state = ExRatatui.text_input_new()
    iex> is_reference(state)
    true

# `text_input_set_value`

```elixir
@spec text_input_set_value(reference(), String.t()) :: :ok
```

Sets the text value on the TextInput state.

The cursor is moved to the end of the new value.

## Examples

    iex> state = ExRatatui.text_input_new()
    iex> ExRatatui.text_input_set_value(state, "hello")
    :ok
    iex> ExRatatui.text_input_get_value(state)
    "hello"

# `textarea_cursor`

```elixir
@spec textarea_cursor(reference()) :: {non_neg_integer(), non_neg_integer()}
```

Returns the cursor position as `{row, col}` from the Textarea state.

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> ExRatatui.textarea_cursor(state)
    {0, 0}

# `textarea_get_value`

```elixir
@spec textarea_get_value(reference()) :: String.t()
```

Returns the current text from the Textarea as a string (lines joined with \n).

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> ExRatatui.textarea_get_value(state)
    ""

# `textarea_handle_key`

```elixir
@spec textarea_handle_key(reference(), String.t(), [String.t()]) :: :ok
```

Forwards a key event with modifiers to the Textarea state.

`modifiers` is a list of active modifier strings (e.g. `["ctrl"]`),
defaults to `[]`. The textarea supports Emacs-style shortcuts
(Ctrl+Z undo, Ctrl+Y redo, etc.).

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> ExRatatui.textarea_handle_key(state, "h", [])
    :ok
    iex> ExRatatui.textarea_handle_key(state, "i", [])
    :ok
    iex> ExRatatui.textarea_get_value(state)
    "hi"

# `textarea_line_count`

```elixir
@spec textarea_line_count(reference()) :: non_neg_integer()
```

Returns the number of lines in the Textarea state.

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> ExRatatui.textarea_line_count(state)
    1

# `textarea_new`

```elixir
@spec textarea_new() :: reference()
```

Creates a new Textarea state.

Returns a reference to the Rust-side state (ResourceArc). Pass this
reference as the `:state` field of `%ExRatatui.Widgets.Textarea{}`.

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> is_reference(state)
    true

# `textarea_set_value`

```elixir
@spec textarea_set_value(reference(), String.t()) :: :ok
```

Sets the text value on the Textarea state.

## Examples

    iex> state = ExRatatui.textarea_new()
    iex> ExRatatui.textarea_set_value(state, "hello\nworld")
    :ok
    iex> ExRatatui.textarea_get_value(state)
    "hello\nworld"

---

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