# `Harlock.TextBuffer`
[🔗](https://github.com/thatsme/harlock/blob/v0.2.0/lib/harlock/text_buffer.ex#L1)

Pure helpers for editing a `(value, cursor)` pair.

The cursor is an integer index into `String.graphemes(value)` — `0` is
before the first grapheme, `length(graphemes)` is after the last. This
matches what users expect ("the cursor is between graphemes, not codepoints").
Display column position is a separate concern, computed by `cursor_column/2`
using `Harlock.Width`.

These functions are owned by the app's model. The `text_input` element
is a dumb renderer that reads `:value` and `:cursor` out of the model
— no internal state. The app's `update/2` calls these helpers to react
to key events.

Typical usage:

    def update({:key, key, mods}, model) do
      case Harlock.Focus.current() do
        :search ->
          case TextBuffer.apply_key({:key, key, mods}, model.search, model.search_cursor) do
            {:edit, v, c} -> %{model | search: v, search_cursor: c}
            :submit -> trigger_search(model)
            :noop -> model
          end

        _ ->
          model
      end
    end

# `cursor`

```elixir
@type cursor() :: non_neg_integer()
```

# `input_event`

```elixir
@type input_event() :: {:edit, String.t(), cursor()} | :submit | :noop
```

# `apply_key`

```elixir
@spec apply_key({:key, any(), [atom()]}, String.t(), cursor()) :: input_event()
```

Map a raw `{:key, key, mods}` event to an edit. Returns one of:

  * `{:edit, value, cursor}` — model should adopt the new pair
  * `:submit` — user pressed Enter; app handles submission
  * `:noop` — key isn't part of the text-input vocabulary, ignore

# `cursor_column`

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

Display column corresponding to a cursor index — the sum of display widths
of all graphemes before the cursor. Useful for positioning the terminal
cursor in the renderer.

# `delete_backward`

```elixir
@spec delete_backward(String.t(), cursor()) :: {String.t(), cursor()}
```

Delete the grapheme before the cursor. No-op at position 0.

# `delete_forward`

```elixir
@spec delete_forward(String.t(), cursor()) :: {String.t(), cursor()}
```

Delete the grapheme after the cursor. No-op at end.

# `end_`

```elixir
@spec end_(String.t()) :: cursor()
```

Move cursor to the end (one past the last grapheme).

# `home`

```elixir
@spec home() :: cursor()
```

Move cursor to the start.

# `insert`

```elixir
@spec insert(String.t(), cursor(), String.t()) :: {String.t(), cursor()}
```

Insert a string at the cursor. Returns the new value and cursor.

# `move_left`

```elixir
@spec move_left(cursor()) :: cursor()
```

Move cursor one grapheme left. Clamps at 0.

# `move_right`

```elixir
@spec move_right(String.t(), cursor()) :: cursor()
```

Move cursor one grapheme right. Clamps at the end.

---

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