# `Minga.Editing.Text.Readable`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga/editing/text/readable.ex#L1)

Protocol for read-only access to text content.

Any data structure that holds text and can answer positional queries
can implement this protocol. The vim motion, text-object, and operator
systems dispatch through `Readable` so they work with any text container:
gap buffers (`Document`) or future types like ropes and structured
content adapters.

## Required callbacks

| Function             | Purpose                                       |
|----------------------|-----------------------------------------------|
| `content/1`          | Full text as a single string                  |
| `line_at/2`          | Nth line (0-indexed), nil if out of range     |
| `line_count/1`       | Total number of lines (always >= 1)           |
| `offset_to_position/2` | Byte offset to `{line, col}` position       |

# `position`

```elixir
@type position() :: {non_neg_integer(), non_neg_integer()}
```

A zero-indexed `{line, col}` cursor position.

# `t`

```elixir
@type t() :: term()
```

All the types that implement this protocol.

# `content`

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

Returns the full text content as a single string with newline separators.

# `line_at`

```elixir
@spec line_at(t(), non_neg_integer()) :: String.t() | nil
```

Returns the line at the given 0-based index, or nil if out of range.

# `line_count`

```elixir
@spec line_count(t()) :: pos_integer()
```

Returns the total number of lines. Always >= 1 (empty text has one empty line).

# `offset_to_position`

```elixir
@spec offset_to_position(t(), non_neg_integer()) :: position()
```

Converts a byte offset (from the start of content) to a `{line, col}` position.

Used by word motions that search through the full text content and need
to convert a match position back to line/col coordinates.

---

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