Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/cursor/callbacks.ex
defmodule Raxol.Terminal.Cursor.Callbacks do
@moduledoc """
Handles GenServer callbacks for the cursor manager.
Extracted from Raxol.Terminal.Cursor.Manager to reduce file size.
"""
alias Raxol.Core.Runtime.Log
alias Raxol.Terminal.Cursor.Movement
@doc """
Handles GenServer call for getting cursor position.
"""
def handle_get_position(state) do
Raxol.Core.Runtime.Log.debug(
"Getting cursor position: {#{state.row}, #{state.col}}"
)
{state.position, state}
end
@doc """
Handles GenServer call for setting cursor position.
"""
def handle_set_position(state, row, col) do
Raxol.Core.Runtime.Log.debug(
"Setting cursor position from {#{state.row}, #{state.col}} to {#{row}, #{col}}"
)
new_state = %{state | row: row, col: col, position: {row, col}}
# Debug: log the new state
Raxol.Core.Runtime.Log.debug(
"New cursor state: row=#{new_state.row}, col=#{new_state.col}, position=#{inspect(new_state.position)}"
)
{:ok, new_state}
end
@doc """
Handles GenServer call for moving cursor in a direction.
"""
def handle_move_cursor(state, direction, count) do
new_state =
case direction do
:up -> Movement.move_up(state, count, 80, 24)
:down -> Movement.move_down(state, count, 80, 24)
:left -> Movement.move_left(state, count, 80, 24)
:right -> Movement.move_right(state, count, 80, 24)
end
{:ok, new_state}
end
@doc """
Handles GenServer call for getting cursor visibility.
"""
def handle_get_visibility(state) do
{state.visible, state}
end
@doc """
Handles GenServer call for setting cursor visibility.
"""
def handle_set_visibility(state, visible) do
new_state = %{
state
| visible: visible,
state:
case visible do
true -> :visible
false -> :hidden
end
}
{:ok, new_state}
end
@doc """
Handles GenServer cast for setting cursor visibility.
"""
def handle_set_visibility_cast(state, visible) do
new_state = %{
state
| visible: visible,
state:
case visible do
true -> :visible
false -> :hidden
end
}
{new_state}
end
@doc """
Handles GenServer call for getting cursor style.
"""
def handle_get_style(state) do
{state.style, state}
end
@doc """
Handles GenServer call for setting cursor style.
"""
def handle_set_style(state, style) do
{:ok, %{state | style: style}}
end
@doc """
Handles GenServer call for getting cursor blink state.
"""
def handle_get_blink(state) do
{state.blinking, state}
end
@doc """
Handles GenServer call for setting cursor blink state.
"""
def handle_set_blink(state, blink) do
new_state = %{state | blinking: blink}
_ =
case blink do
true -> schedule_blink()
false -> cancel_blink(state.blink_timer)
end
{:ok, new_state}
end
@doc """
Handles GenServer call for setting custom cursor shape.
"""
def handle_set_custom_shape(state, shape, params) do
{:ok,
%{
state
| style: :custom,
custom_shape: shape,
custom_dimensions: params,
shape: params
}}
end
@doc """
Handles GenServer call for updating cursor position.
"""
def handle_update_position(state, row, col) do
{:ok, %{state | row: row, col: col, position: {row, col}}}
end
@doc """
Handles GenServer call for resetting cursor position.
"""
def handle_reset_position(state) do
{:ok, %{state | row: 0, col: 0, position: {0, 0}}}
end
@doc """
Handles GenServer call for updating position from text.
"""
def handle_update_position_from_text(state, text) do
new_col = state.col + String.length(text)
new_state = %{state | col: new_col, position: {state.row, new_col}}
{:ok, new_state}
end
@doc """
Handles GenServer call for updating blink state.
"""
def handle_update_blink(state) do
new_blink_state = !state.blink
new_state = %{state | blink: new_blink_state}
{new_blink_state, new_state}
end
@doc """
Handles GenServer call for getting cursor state.
"""
def handle_get_state(state) do
{state, state}
end
@doc """
Handles GenServer call for getting cursor state atom.
"""
def handle_get_state_atom(state) do
{state.state, state}
end
@doc """
Handles GenServer call for moving cursor down.
"""
def handle_move_down(state, count, _width, height) do
# Move the cursor down by count lines, respecting margins
new_row = min(Map.get(state, :bottom_margin, height - 1), state.row + count)
new_state = %{state | row: new_row, position: {new_row, state.col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor up.
"""
def handle_move_up(state, lines, _width, _height) do
# Move the cursor up by lines, respecting margins
new_row = max(Map.get(state, :top_margin, 0), state.row - lines)
new_state = %{state | row: new_row, position: {new_row, state.col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor right.
"""
def handle_move_right(state, cols, _width, _height) do
# Move the cursor right by cols
new_col = state.col + cols
new_state = %{state | col: new_col, position: {state.row, new_col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor left.
"""
def handle_move_left(state, cols, _width, _height) do
# Move the cursor left by cols
new_col = max(0, state.col - cols)
new_state = %{state | col: new_col, position: {state.row, new_col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor to position.
"""
def handle_move_to(state, row, col) do
# Move cursor to specific position
new_state = %{state | row: row, col: col, position: {row, col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor to column.
"""
def handle_move_to_column(state, column) do
# Move cursor to specific column
new_state = %{state | col: column, position: {state.row, column}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor to column with bounds.
"""
def handle_move_to_column_bounded(state, column, width, _height) do
# Move cursor to specific column with bounds clamping
clamped_col = max(0, min(column, width - 1))
new_state = %{state | col: clamped_col, position: {state.row, clamped_col}}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor to position with bounds.
"""
def handle_move_to_bounded(state, row, col, width, height) do
# Move cursor to specific position with bounds clamping
clamped_row = max(0, min(row, height - 1))
clamped_col = max(0, min(col, width - 1))
new_state = %{
state
| row: clamped_row,
col: clamped_col,
position: {clamped_row, clamped_col}
}
{new_state, new_state}
end
@doc """
Handles GenServer call for moving cursor to bounded position.
"""
def handle_move_to_bounded_position(state, row, col, width, height) do
# Move cursor to specific position with bounds clamping
clamped_row = max(0, min(row, height - 1))
clamped_col = max(0, min(col, width - 1))
new_state = %{
state
| row: clamped_row,
col: clamped_col,
position: {clamped_row, clamped_col}
}
{:ok, new_state}
end
@doc """
Handles GenServer call for setting state atom.
"""
def handle_set_state_atom(state, state_atom) do
new_state =
case state_atom do
:visible -> %{state | visible: true, state: :visible}
:hidden -> %{state | visible: false, state: :hidden}
:blinking -> %{state | blinking: true, blink: true, state: :blinking}
_ -> state
end
{:ok, new_state}
end
@doc """
Handles GenServer call for unknown requests.
"""
def handle_unknown_request(request, state) do
Log.warning("Unknown request: #{inspect(request)}")
{{:error, :unknown_request}, state}
end
@doc """
Handles GenServer info for blink timer.
"""
def handle_blink_info(state, _timer_id) do
case state.blinking do
true ->
new_blink_state = !state.blink
new_state = %{state | blink: new_blink_state}
schedule_blink()
{new_state}
false ->
{state}
end
end
# --- Private Functions ---
defp schedule_blink do
timer_id = System.unique_integer([:positive])
Process.send_after(self(), {:blink, timer_id}, 500)
end
defp cancel_blink(nil), do: :ok
defp cancel_blink(timer_id), do: _ = Process.cancel_timer(timer_id)
end