Packages

Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.

Current section

Files

Jump to
raxol_terminal lib raxol terminal cursor.ex
Raw

lib/raxol/terminal/cursor.ex

defmodule Raxol.Terminal.Cursor do
@moduledoc """
Provides cursor manipulation functions for the terminal emulator.
This module handles operations like moving the cursor, setting its visibility,
and managing cursor state.
"""
defstruct [:position, :shape, :visible, :saved_position]
@type t :: %__MODULE__{
position: {non_neg_integer(), non_neg_integer()},
shape: atom(),
visible: boolean(),
saved_position: {non_neg_integer(), non_neg_integer()} | nil
}
@doc """
Creates a new cursor with default settings.
"""
def new do
%__MODULE__{
position: {0, 0},
shape: :block,
visible: true,
saved_position: nil
}
end
@doc """
Moves the cursor up by the specified number of lines.
"""
def move_up(emulator, lines) do
{x, y} = emulator.cursor.position
new_y = max(0, y - lines)
%{emulator | cursor: %{emulator.cursor | position: {x, new_y}}}
end
@doc """
Moves the cursor down by the specified number of lines.
"""
def move_down(emulator, lines) do
{x, y} = emulator.cursor.position
new_y = min(emulator.height - 1, y + lines)
%{emulator | cursor: %{emulator.cursor | position: {x, new_y}}}
end
@doc """
Moves the cursor right by the specified number of columns.
"""
def move_right(emulator, cols) do
{x, y} = emulator.cursor.position
new_x = min(emulator.width - 1, x + cols)
%{emulator | cursor: %{emulator.cursor | position: {new_x, y}}}
end
@doc """
Moves the cursor left by the specified number of columns.
"""
def move_left(emulator, cols) do
{x, y} = emulator.cursor.position
new_x = max(0, x - cols)
%{emulator | cursor: %{emulator.cursor | position: {new_x, y}}}
end
@doc """
Moves the cursor down and to the beginning of the line.
"""
def move_down_and_home(emulator, lines) do
emulator
|> move_down(lines)
|> move_to_column(0)
end
@doc """
Moves the cursor up and to the beginning of the line.
"""
def move_up_and_home(emulator, lines) do
emulator
|> move_up(lines)
|> move_to_column(0)
end
@doc """
Moves the cursor to the specified column.
"""
def move_to_column(emulator, col) do
{_, y} = emulator.cursor.position
new_x = max(0, min(emulator.width - 1, col))
%{emulator | cursor: %{emulator.cursor | position: {new_x, y}}}
end
@doc """
Moves the cursor to the specified position.
"""
def move_to(%{cursor: nil} = emulator, {row, col}) do
new_x = max(0, min(emulator.width - 1, col))
new_y = max(0, min(emulator.height - 1, row))
%{emulator | cursor: %{position: {new_x, new_y}, row: new_y, col: new_x}}
end
def move_to(emulator, {row, col}) do
new_x = max(0, min(emulator.width - 1, col))
new_y = max(0, min(emulator.height - 1, row))
%{emulator | cursor: %{emulator.cursor | position: {new_x, new_y}}}
end
@doc """
Sets the cursor visibility.
"""
def set_visible(emulator, visible) do
%{emulator | cursor: %{emulator.cursor | visible: visible}}
end
@doc """
Moves the cursor forward by the specified number of columns.
"""
def move_forward(emulator, cols) do
move_right(emulator, cols)
end
@doc """
Moves the cursor backward by the specified number of columns.
"""
def move_backward(emulator, cols) do
move_left(emulator, cols)
end
@doc """
Moves the cursor to the specified row and column.
"""
def move_to(%{cursor: nil} = emulator, row, col) do
move_to(emulator, {row, col})
end
def move_to(%__MODULE__{} = cursor, row, col) do
move_to(cursor, {row, col}, 999_999, 999_999)
end
def move_to(emulator, row, col) do
move_to(emulator, {row, col})
end
@doc """
Gets the current cursor position.
"""
def get_position(emulator) do
emulator.cursor.position
end
@doc """
Sets the cursor shape.
"""
def set_shape(emulator, shape) do
%{emulator | cursor: %{emulator.cursor | shape: shape}}
end
@doc """
Moves the cursor to the specified position, taking into account the screen width and height.
"""
def move_to(cursor, {x, y}, width, height) do
x = max(0, min(x, width - 1))
y = max(0, min(y, height - 1))
%{cursor | position: {x, y}}
end
# === Additional Cursor Functions ===
@doc """
Checks if the cursor is visible.
"""
def visible?(emulator) do
emulator.cursor.visible
end
@doc """
Sets the cursor visibility.
"""
def set_visibility(emulator, visible) do
set_visible(emulator, visible)
end
@doc """
Sets the cursor style.
"""
def set_style(emulator, style) do
%{emulator | cursor: %{emulator.cursor | style: style}}
end
@doc """
Sets the cursor position.
"""
def set_position(%{cursor: nil} = emulator, {col, row}) do
# Create a cursor if it doesn't exist
new_x = max(0, min(emulator.width - 1, col))
new_y = max(0, min(emulator.height - 1, row))
%{emulator | cursor: %{position: {new_x, new_y}, row: new_y, col: new_x}}
end
def set_position(emulator, position) do
move_to(emulator, position)
end
@doc """
Sets the cursor color.
"""
def set_color(emulator, color) do
%{emulator | cursor: %{emulator.cursor | color: color}}
end
@doc """
Sets the cursor blink state.
"""
def set_blink(emulator, blink) do
%{emulator | cursor: %{emulator.cursor | blink: blink}}
end
@doc """
Resets the cursor color to default.
"""
def reset_color(emulator) do
%{emulator | cursor: %{emulator.cursor | color: nil}}
end
@doc """
Gets the cursor style.
"""
def get_style(emulator) do
emulator.cursor.style || %{}
end
@doc """
Moves the cursor relative to its current position.
"""
def move_relative(%__MODULE__{position: {x, y}} = cursor, dx, dy) do
new_x = max(0, x + dx)
new_y = max(0, y + dy)
%{cursor | position: {new_x, new_y}}
end
@doc """
Saves the current cursor position.
"""
def save(%__MODULE__{position: position} = cursor) do
%{cursor | saved_position: position}
end
@doc """
Restores the cursor to a previously saved position.
"""
def restore(cursor, saved_cursor) do
case saved_cursor.saved_position do
nil -> cursor
position -> %{cursor | position: position}
end
end
end