TermUI.Renderer.CursorOptimizer (TermUI v1.0.0)

View Source

Optimizes cursor movement by selecting the cheapest movement option.

Instead of always using absolute positioning (ESC[{row};{col}H), this module calculates the byte cost of various movement options and selects the minimum. This can reduce cursor movement overhead by 40%+ compared to naive positioning.

Movement Options

  • Absolute positioning: ESC[{r};{c}H (6-10 bytes)
  • Relative up/down/left/right: ESC[{n}A/B/C/D (4-6 bytes)
  • Carriage return: \r (1 byte)
  • Newline: \n (1 byte)
  • Home: ESC[H (3 bytes)
  • Literal spaces for small rightward moves (1 byte each)

Usage

# Create optimizer with initial position
optimizer = CursorOptimizer.new()

# Get optimal movement sequence
{sequence, new_optimizer} = CursorOptimizer.move_to(optimizer, 5, 10)

# After text output, advance cursor
new_optimizer = CursorOptimizer.advance(optimizer, 5)

Summary

Functions

Advances the cursor position after text output.

Returns the total bytes saved through optimization.

Calculates the byte cost of absolute positioning.

Calculates the byte cost of carriage return (move to column 1).

Calculates the byte cost of moving cursor down.

Calculates the byte cost of home (move to 1,1).

Calculates the byte cost of moving cursor left.

Calculates the byte cost of newline (move down one row).

Calculates the byte cost of moving cursor right.

Calculates the byte cost of moving cursor up.

Returns the maximum cursor position supported.

Moves the cursor to the target position using the optimal movement sequence.

Creates a new cursor optimizer with cursor at position (1, 1).

Creates a cursor optimizer with cursor at the specified position.

Finds the optimal movement sequence from current to target position.

Returns the current cursor position as {row, col}.

Resets the cursor position to (1, 1).

Types

t()

@type t() :: %TermUI.Renderer.CursorOptimizer{
  bytes_saved: non_neg_integer(),
  col: pos_integer(),
  row: pos_integer()
}

Functions

advance(optimizer, cols)

@spec advance(t(), non_neg_integer()) :: t()

Advances the cursor position after text output.

Call this after outputting text to keep cursor position synchronized. The new column position is clamped to @max_cursor_pos to prevent integer overflow issues with ANSI escape sequences.

bytes_saved(cursor_optimizer)

@spec bytes_saved(t()) :: non_neg_integer()

Returns the total bytes saved through optimization.

cost_absolute(row, col)

@spec cost_absolute(pos_integer(), pos_integer()) :: pos_integer()

Calculates the byte cost of absolute positioning.

ESC[{row};{col}H costs 4 + digits(row) + digits(col) bytes.

cost_cr()

@spec cost_cr() :: pos_integer()

Calculates the byte cost of carriage return (move to column 1).

cost_down(n)

@spec cost_down(pos_integer()) :: pos_integer()

Calculates the byte cost of moving cursor down.

ESC[{n}B costs 3 + digits(n) bytes, or 3 bytes for n=1.

cost_home()

@spec cost_home() :: pos_integer()

Calculates the byte cost of home (move to 1,1).

cost_left(n)

@spec cost_left(pos_integer()) :: pos_integer()

Calculates the byte cost of moving cursor left.

ESC[{n}D costs 3 + digits(n) bytes, or 3 bytes for n=1.

cost_lf()

@spec cost_lf() :: pos_integer()

Calculates the byte cost of newline (move down one row).

cost_right(n)

@spec cost_right(pos_integer()) :: pos_integer()

Calculates the byte cost of moving cursor right.

ESC[{n}C costs 3 + digits(n) bytes, or 3 bytes for n=1.

cost_up(n)

@spec cost_up(pos_integer()) :: pos_integer()

Calculates the byte cost of moving cursor up.

ESC[{n}A costs 3 + digits(n) bytes, or 3 bytes for n=1.

max_position()

@spec max_position() :: pos_integer()

Returns the maximum cursor position supported.

Positions beyond this value may cause undefined behavior on some terminals.

move_to(optimizer, target_row, target_col)

@spec move_to(t(), pos_integer(), pos_integer()) :: {iodata(), t()}

Moves the cursor to the target position using the optimal movement sequence.

Returns {sequence, updated_optimizer} where sequence is iodata containing the escape sequences for the movement.

Examples

iex> optimizer = CursorOptimizer.new()
iex> {seq, _opt} = CursorOptimizer.move_to(optimizer, 1, 5)
iex> IO.iodata_to_binary(seq)
"\e[5C"

new()

@spec new() :: t()

Creates a new cursor optimizer with cursor at position (1, 1).

new(row, col)

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

Creates a cursor optimizer with cursor at the specified position.

optimal_move(from_row, from_col, to_row, to_col)

@spec optimal_move(pos_integer(), pos_integer(), pos_integer(), pos_integer()) ::
  {iodata(), pos_integer()}

Finds the optimal movement sequence from current to target position.

Returns {sequence, cost} where sequence is iodata.

position(cursor_optimizer)

@spec position(t()) :: {pos_integer(), pos_integer()}

Returns the current cursor position as {row, col}.

reset(optimizer)

@spec reset(t()) :: t()

Resets the cursor position to (1, 1).