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

A per-connection terminal session backed by an in-memory writer.

Where `ExRatatui.run/1` and `ExRatatui.draw/2` are tied to the OS process'
real tty (raw mode, alternate screen, `SIGWINCH`), a `Session` is a
self-contained ratatui terminal whose output goes into a buffer and whose
input arrives as raw bytes from a transport you control. That makes it
the right primitive for serving a TUI over SSH, multiplexing several
TUIs in one BEAM node, or any context where the "terminal" lives somewhere
other than the local process.

Sessions touch nothing on the host tty, so they are safe to create and
drive concurrently from `async: true` tests, GenServers, or background
tasks.

## Lifecycle

    session = ExRatatui.Session.new(80, 24)
    :ok     = ExRatatui.Session.draw(session, [{paragraph, rect}])
    bytes   = ExRatatui.Session.take_output(session)
    events  = ExRatatui.Session.feed_input(session, "\e[A")
    :ok     = ExRatatui.Session.resize(session, 100, 30)
    :ok     = ExRatatui.Session.close(session)

Each call is a thin wrapper over a Rust NIF, so a session is just an
Elixir struct holding a `t:reference/0` to a `ResourceArc`. When the
struct is garbage collected the underlying resource is freed; `close/1`
is the deterministic version of the same cleanup and is idempotent.

## Transport responsibilities

A session does not own a socket or do any I/O of its own. The transport
glue is responsible for:

  1. Calling `draw/2` whenever the app wants to repaint, then handing the
     result of `take_output/1` to the wire.
  2. Feeding inbound transport bytes through `feed_input/2` and dispatching
     the returned `t:ExRatatui.Event.t/0` values to the app.
  3. Calling `resize/3` when the remote pty changes size.
  4. Calling `close/1` when the connection ends.

See `ExRatatui.SSH` for an OTP `:ssh_server_channel`-based transport.

# `t`

```elixir
@type t() :: %ExRatatui.Session{ref: reference()}
```

# `close`

```elixir
@spec close(t()) :: :ok
```

Closes the session, dropping its rendering terminal.

Idempotent — calling `close/1` more than once is safe and always
returns `:ok`. After closing, `draw/2` and `resize/3` will return
`{:error, _}`, but `feed_input/2` continues to work so a transport
can drain any trailing input bytes.

## Examples

    iex> session = ExRatatui.Session.new(20, 5)
    iex> ExRatatui.Session.close(session)
    :ok
    iex> ExRatatui.Session.close(session)
    :ok

# `draw`

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

Renders a list of `{widget, rect}` tuples into the session's writer.

Identical in shape to `ExRatatui.draw/2`, but the encoded ANSI bytes
accumulate in the session's in-memory buffer instead of going to the
real tty. Drain them with `take_output/1`.

Returns `:ok` on success, or `{:error, reason}` if the session has been
closed or a widget cannot be encoded.

## Examples

    iex> alias ExRatatui.Widgets.Paragraph
    iex> alias ExRatatui.Layout.Rect
    iex> session = ExRatatui.Session.new(20, 5)
    iex> ExRatatui.Session.draw(session, [{%Paragraph{text: "hi"}, %Rect{x: 0, y: 0, width: 20, height: 5}}])
    :ok

# `feed_input`

```elixir
@spec feed_input(t(), binary()) :: [ExRatatui.Event.t()]
```

Feeds raw transport bytes through the session's ANSI input parser.

Returns a list of decoded `t:ExRatatui.Event.t/0` structs, in the order
the parser produced them. Bytes that only partially form an escape
sequence stay buffered inside the session — feed the next chunk and
the parser will pick up where it left off. This is essential for SSH
and any other byte-stream transport that may chunk a single keystroke
across multiple packets.

Unlike `draw/2`, this still works after `close/1` — the input parser
outlives the rendering terminal so a transport can drain trailing
input bytes after deciding to tear down rendering.

## Examples

    iex> session = ExRatatui.Session.new(20, 5)
    iex> ExRatatui.Session.feed_input(session, "a")
    [%ExRatatui.Event.Key{code: "a", modifiers: [], kind: "press"}]

# `new`

```elixir
@spec new(pos_integer(), pos_integer()) :: t()
```

Creates a new session at the given dimensions.

No OS terminal state is touched — the session writes into an in-memory
buffer that the transport drains via `take_output/1`. Both dimensions
must be at least `1`.

## Examples

    iex> session = ExRatatui.Session.new(80, 24)
    iex> ExRatatui.Session.size(session)
    {80, 24}
    iex> ExRatatui.Session.close(session)
    :ok

# `reset_parser`

```elixir
@spec reset_parser(t()) :: :ok
```

Resets the session's input parser, discarding any buffered partial
escape sequence.

Used by the SSH transport's Esc timeout: after a bare `0x1B` with no
follow-up bytes, the VTE state machine is stuck in the Escape state.
Calling this drops that state so the next byte is parsed from Ground.

## Examples

    iex> session = ExRatatui.Session.new(20, 5)
    iex> ExRatatui.Session.reset_parser(session)
    :ok

# `resize`

```elixir
@spec resize(t(), pos_integer(), pos_integer()) :: :ok | {:error, term()}
```

Resizes the session's viewport to `width` x `height`.

The next `draw/2` will paint at the new dimensions and the buffer will
contain a clear-screen sequence the transport must forward. Returns
`{:error, reason}` if the session has been closed.

## Examples

    iex> session = ExRatatui.Session.new(20, 5)
    iex> :ok = ExRatatui.Session.resize(session, 100, 30)
    iex> ExRatatui.Session.size(session)
    {100, 30}

# `size`

```elixir
@spec size(t()) :: {pos_integer(), pos_integer()}
```

Returns the session's current `{width, height}`.

## Examples

    iex> session = ExRatatui.Session.new(80, 24)
    iex> ExRatatui.Session.size(session)
    {80, 24}

# `take_output`

```elixir
@spec take_output(t()) :: binary()
```

Drains the session's pending output bytes.

Returns whatever ratatui has written into the in-memory buffer since the
last drain — typically the bytes the transport should ship to the
remote tty. The internal buffer is emptied as a side effect, so a
follow-up call with no intervening `draw/2` returns `<<>>`.

## Examples

    iex> session = ExRatatui.Session.new(20, 5)
    iex> :ok = ExRatatui.Session.draw(session, [])
    iex> bytes = ExRatatui.Session.take_output(session)
    iex> byte_size(bytes) > 0
    true
    iex> ExRatatui.Session.take_output(session)
    ""

---

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