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/ansi/terminal_state.ex
defmodule Raxol.Terminal.ANSI.TerminalState do
@moduledoc """
Manages terminal state operations for ANSI escape sequences.
"""
defstruct [
:state_stack,
:current_state,
:saved_states,
:max_saved_states
]
@type state_stack :: list(map())
@type t :: %__MODULE__{
state_stack: state_stack(),
current_state: map(),
saved_states: list(map()),
max_saved_states: integer()
}
@doc """
Creates a new terminal state with default settings.
"""
def new(opts \\ []) do
%__MODULE__{
state_stack: [],
current_state: default_state(),
saved_states: [],
max_saved_states: Keyword.get(opts, :max_saved_states, 10)
}
end
@doc """
Gets the current state stack.
"""
def get_state_stack(%__MODULE__{} = state) do
state.state_stack
end
@doc """
Updates the state stack.
"""
def update_state_stack(%__MODULE__{} = state, new_stack)
when is_list(new_stack) do
%{state | state_stack: new_stack}
end
@doc """
Saves the current state.
"""
def save(%__MODULE__{} = state) do
saved_states = [state.current_state | state.saved_states]
saved_states =
case length(saved_states) > state.max_saved_states do
true ->
Enum.take(saved_states, state.max_saved_states)
false ->
saved_states
end
%{state | saved_states: saved_states}
end
@doc """
Restores the most recently saved state.
"""
def restore(%__MODULE__{} = state) do
case state.saved_states do
[saved_state | remaining_states] ->
%{state | current_state: saved_state, saved_states: remaining_states}
[] ->
state
end
end
@doc """
Checks if there are any saved states.
"""
def has_saved_states?(%__MODULE__{} = state) do
state.saved_states != []
end
@doc """
Gets the number of saved states.
"""
def get_saved_states_count(%__MODULE__{} = state) do
length(state.saved_states)
end
@doc """
Clears all saved states.
"""
def clear(%__MODULE__{} = state) do
%{state | saved_states: []}
end
@doc """
Gets the current state.
"""
def get_current_state(%__MODULE__{} = state) do
state.current_state
end
@doc """
Updates the current state.
"""
def update_current_state(%__MODULE__{} = state, new_state)
when is_map(new_state) do
%{state | current_state: new_state}
end
@doc """
Pushes the current state onto the state stack.
"""
def push(%__MODULE__{} = state) do
new_stack = [state.current_state | state.state_stack]
%{state | state_stack: new_stack}
end
@doc """
Pops a state from the state stack.
"""
def pop(%__MODULE__{} = state) do
case state.state_stack do
[popped_state | remaining_stack] ->
{:ok,
%{state | current_state: popped_state, state_stack: remaining_stack}}
[] ->
{:error, :empty_stack}
end
end
@doc """
Gets the current state from the state stack.
"""
def current(%__MODULE__{} = state) do
case state.state_stack do
[current | _] -> current
[] -> nil
end
end
@doc """
Saves the current terminal state to the state stack.
"""
def save_state(stack, state) do
[state | stack]
end
@doc """
Restores the most recently saved terminal state from the state stack.
Returns the restored state and the updated stack.
"""
def restore_state([state | stack]), do: {state, stack}
def restore_state([]), do: {nil, []}
@doc """
Gets the count of states in the state stack.
"""
def count(stack) when is_list(stack) do
length(stack)
end
@doc """
Checks if the state stack is empty.
"""
def empty?(stack) when is_list(stack) do
stack == []
end
@doc """
Clears the terminal state stack.
"""
def clear_state(stack) when is_list(stack) do
[]
end
@doc """
Applies restored data to the emulator state.
"""
def apply_restored_data(emulator, restored_state, fields_to_restore) do
Enum.reduce(fields_to_restore, emulator, fn field, acc ->
case field do
:cursor when is_map(restored_state.cursor) ->
%{acc | cursor: Map.merge(acc.cursor, restored_state.cursor)}
:style when is_map(restored_state.style) ->
%{acc | style: restored_state.style}
:charset_state when is_map(restored_state.charset_state) ->
%{acc | charset_state: restored_state.charset_state}
:mode_manager when is_map(restored_state.mode_manager) ->
%{
acc
| mode_manager:
Map.merge(acc.mode_manager, restored_state.mode_manager)
}
:scroll_region ->
%{acc | scroll_region: restored_state.scroll_region}
:cursor_style ->
%{acc | cursor_style: restored_state.cursor_style}
_ ->
acc
end
end)
end
defp default_state do
%{
cursor_visible: true,
cursor_style: :block,
cursor_blink: true,
cursor_position: {0, 0},
scroll_region: nil,
origin_mode: false,
auto_wrap: true,
insert_mode: false,
line_feed_mode: false,
reverse_video: false,
attributes: %{},
colors: %{
foreground: 7,
background: 0
}
}
end
end