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/modes/mode_state_manager.ex
defmodule Raxol.Terminal.Modes.ModeStateManager do
@moduledoc """
Manages the state of terminal modes, including transitions and validation.
Handles mode dependencies, conflicts, and state persistence.
"""
use Raxol.Core.Behaviours.BaseManager
require Logger
alias Raxol.Terminal.Modes.Types.ModeTypes
require Raxol.Core.Runtime.Log
defstruct modes: %{}
@type t :: %__MODULE__{}
# Client API
# BaseManager provides start_link
@doc """
Creates a new mode state with default values.
"""
@spec new() :: t()
def new do
%__MODULE__{
modes: initialize_default_modes()
}
end
@doc """
Sets a mode to a specific value.
"""
@spec set_mode(t(), atom(), ModeTypes.mode_value()) ::
{:ok, t()} | {:error, term()}
def set_mode(state, mode_name, value) do
with {:ok, mode_def} <- validate_mode(mode_name),
:ok <- validate_dependencies(state, mode_def),
:ok <- validate_conflicts(state, mode_def) do
new_state = put_in(state.modes[mode_name], value)
{:ok, new_state}
end
end
@doc """
Resets a mode to its default value.
"""
@spec reset_mode(t(), atom()) :: {:ok, t()} | {:error, term()}
def reset_mode(state, mode_name) do
with {:ok, mode_def} <- validate_mode(mode_name) do
new_state = put_in(state.modes[mode_name], mode_def.default_value)
{:ok, new_state}
end
end
@doc """
Checks if a mode is enabled.
"""
@spec mode_enabled?(t(), atom()) :: boolean()
def mode_enabled?(state, mode_name) do
case Map.get(state.modes, mode_name) do
nil -> false
value -> value == true or value == :enabled
end
end
# Server Callbacks
@impl true
def init_manager(_init_arg) do
{:ok, new()}
end
@impl true
def handle_manager_call(:get_state, _from, state) do
{:reply, state, state}
end
@impl true
def handle_manager_call({:set_mode, mode_name, value}, _from, state) do
case set_mode(state, mode_name, value) do
{:ok, new_state} -> {:reply, :ok, new_state}
{:error, reason} -> {:reply, {:error, reason}, state}
end
end
@impl true
def handle_manager_call({:reset_mode, mode_name}, _from, state) do
case reset_mode(state, mode_name) do
{:ok, new_state} -> {:reply, :ok, new_state}
{:error, reason} -> {:reply, {:error, reason}, state}
end
end
@impl true
def handle_manager_call({:get_mode, mode_name}, _from, state) do
value = mode_enabled?(state, mode_name)
{:reply, value, state}
end
# Private Functions
defp initialize_default_modes do
ModeTypes.get_all_modes()
|> Map.values()
|> Enum.map(fn mode_def -> {mode_def.name, mode_def.default_value} end)
|> Map.new()
end
defp validate_mode(mode_name) do
case find_mode_definition(mode_name) do
nil -> {:error, :invalid_mode}
mode_def -> {:ok, mode_def}
end
end
defp find_mode_definition(mode_name) do
ModeTypes.get_all_modes()
|> Map.values()
|> Enum.find(&(&1.name == mode_name))
end
defp validate_dependencies(state, mode_def) do
case check_dependencies(state, mode_def.dependencies) do
:ok -> :ok
{:error, missing} -> {:error, {:missing_dependencies, missing}}
end
end
defp check_dependencies(_state, []), do: :ok
defp check_dependencies(state, [dep | rest]) do
case mode_enabled?(state, dep) do
true -> check_dependencies(state, rest)
false -> {:error, [dep]}
end
end
defp validate_conflicts(state, mode_def) do
case check_conflicts(state, mode_def.conflicts) do
:ok -> :ok
{:error, conflicts} -> {:error, {:conflicting_modes, conflicts}}
end
end
defp check_conflicts(_state, []), do: :ok
defp check_conflicts(state, [conflict | rest]) do
case mode_enabled?(state, conflict) do
true -> {:error, [conflict]}
false -> check_conflicts(state, rest)
end
end
end