TermUI.Helpers.CursorHelper (TermUI v1.0.0)

View Source

Helper functions for cursor navigation within lists.

This module provides convenience functions for managing cursor positions in widgets with selectable items (menus, lists, tables, tree views, etc.).

Usage

import TermUI.Helpers.CursorHelper

# Move cursor down with wrapping
new_cursor = move_down(cursor, 1, item_count, wrap: true)

# Move cursor up with clamping
new_cursor = move_up(cursor, 1, item_count)

# Clamp cursor to valid range
new_cursor = clamp_cursor(cursor, 0, item_count - 1)

Common Patterns

All functions work with 0-based cursor indices. The max parameter is typically length(items) - 1 for the last valid index.

Summary

Functions

Clamps the cursor to valid bounds.

Moves cursor to first valid position from the beginning.

Moves cursor to last valid position from the end.

Moves the cursor down (towards higher indices).

Finds the next valid cursor position, skipping invalid positions.

Moves the cursor up (towards lower indices).

Wraps cursor position within valid range.

Functions

clamp_cursor(cursor, min \\ 0, max)

@spec clamp_cursor(integer(), integer(), integer()) :: integer()

Clamps the cursor to valid bounds.

Ensures cursor is within [min, max] range.

Parameters

  • cursor - Current cursor position
  • min - Minimum valid position (default: 0)
  • max - Maximum valid position

Examples

iex> clamp_cursor(5, 0, 3)
3

iex> clamp_cursor(-2, 0, 3)
0

iex> clamp_cursor(2, 0, 3)
2

first_valid(max, valid?)

@spec first_valid(non_neg_integer(), (non_neg_integer() -> boolean())) ::
  non_neg_integer() | nil

Moves cursor to first valid position from the beginning.

Parameters

  • max - Maximum valid position
  • valid? - Function that returns true if position is valid

Examples

# Find first non-disabled item
valid? = fn pos -> pos not in [0, 1] end
first_valid(4, valid?)
# => 2

last_valid(max, valid?)

@spec last_valid(non_neg_integer(), (non_neg_integer() -> boolean())) ::
  non_neg_integer() | nil

Moves cursor to last valid position from the end.

Parameters

  • max - Maximum valid position
  • valid? - Function that returns true if position is valid

Examples

# Find last non-disabled item
valid? = fn pos -> pos not in [3, 4] end
last_valid(4, valid?)
# => 2

move_down(cursor, step \\ 1, max, opts \\ [])

Moves the cursor down (towards higher indices).

Parameters

  • cursor - Current cursor position (0-based)
  • step - Number of positions to move (default: 1)
  • max - Maximum valid cursor position (inclusive)
  • opts - Options:
    • :wrap - If true, wraps from max to 0 (default: false)

Examples

iex> move_down(0, 1, 4)
1

iex> move_down(4, 1, 4)  # At max, clamped
4

iex> move_down(4, 1, 4, wrap: true)  # At max, wraps to 0
0

iex> move_down(2, 3, 4)  # Move 3 positions, clamped to max
4

move_to_next_valid(cursor, direction, max, valid?, opts \\ [])

@spec move_to_next_valid(
  non_neg_integer(),
  :up | :down,
  non_neg_integer(),
  (non_neg_integer() -> boolean()),
  keyword()
) :: non_neg_integer() | nil

Finds the next valid cursor position, skipping invalid positions.

Useful for skipping separators or disabled items in menus.

Parameters

  • cursor - Current cursor position
  • direction - :up or :down
  • max - Maximum valid position
  • valid? - Function that returns true if position is valid
  • opts - Options:
    • :wrap - If true, wraps at boundaries (default: false)
    • :max_attempts - Maximum positions to try (default: max + 1)

Examples

# Skip disabled items (positions 1 and 2)
valid? = fn pos -> pos not in [1, 2] end
move_to_next_valid(0, :down, 4, valid?)
# => 3 (skips 1 and 2)

move_up(cursor, step \\ 1, max, opts \\ [])

Moves the cursor up (towards lower indices).

Parameters

  • cursor - Current cursor position (0-based)
  • step - Number of positions to move (default: 1)
  • max - Maximum valid cursor position (used for wrapping)
  • opts - Options:
    • :wrap - If true, wraps from 0 to max (default: false)

Examples

iex> move_up(2, 1, 4)
1

iex> move_up(0, 1, 4)  # At 0, clamped
0

iex> move_up(0, 1, 4, wrap: true)  # At 0, wraps to max
4

iex> move_up(1, 3, 4)  # Move 3 positions, clamped to 0
0

wrap_cursor(cursor, min \\ 0, max)

@spec wrap_cursor(integer(), integer(), integer()) :: integer()

Wraps cursor position within valid range.

Unlike clamp, wrap treats the range as circular.

Parameters

  • cursor - Current cursor position (can be negative or > max)
  • min - Minimum valid position (default: 0)
  • max - Maximum valid position

Examples

iex> wrap_cursor(5, 0, 3)  # 5 wraps to 1 (5 mod 4 = 1)
1

iex> wrap_cursor(-1, 0, 3)  # -1 wraps to 3
3

iex> wrap_cursor(4, 0, 3)  # 4 wraps to 0
0