Harlock.TextBuffer (harlock v0.2.0)

Copy Markdown View Source

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

Summary

Functions

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

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 the grapheme before the cursor. No-op at position 0.

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

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

Move cursor to the start.

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

Move cursor one grapheme left. Clamps at 0.

Move cursor one grapheme right. Clamps at the end.

Types

cursor()

@type cursor() :: non_neg_integer()

input_event()

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

Functions

apply_key(arg1, value, cursor)

@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(value, cursor)

@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(value, cursor)

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

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

delete_forward(value, cursor)

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

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

end_(value)

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

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

home()

@spec home() :: cursor()

Move cursor to the start.

insert(value, cursor, str)

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

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

move_left(cursor)

@spec move_left(cursor()) :: cursor()

Move cursor one grapheme left. Clamps at 0.

move_right(value, cursor)

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

Move cursor one grapheme right. Clamps at the end.