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
raxol lib raxol terminal mode_state.ex
Raw

lib/raxol/terminal/mode_state.ex

defmodule Raxol.Terminal.ModeState do
@moduledoc """
Manages terminal mode state and transitions.
This module is responsible for:
- Managing mode state
- Handling mode transitions
- Validating mode changes
- Providing mode state queries
"""
require Raxol.Core.Runtime.Log
# DEC Private Mode codes and their corresponding mode atoms
@dec_private_modes %{
# Cursor Keys Mode
1 => :decckm,
# 132 Column Mode
3 => :deccolm_132,
# 80 Column Mode
80 => :deccolm_80,
# Screen Mode (reverse)
5 => :decscnm,
# Origin Mode
6 => :decom,
# Auto Wrap Mode
7 => :decawm,
# Auto Repeat Mode
8 => :decarm,
# Interlace Mode
9 => :decinlm,
# Start Blinking Cursor
12 => :att_blink,
# Text Cursor Enable Mode
25 => :dectcem,
# Use Alternate Screen Buffer (Simple)
47 => :dec_alt_screen,
# Send Mouse X & Y on button press
1000 => :mouse_report_x10,
# Use Cell Motion Mouse Tracking
1002 => :mouse_report_cell_motion,
# Send FocusIn/FocusOut events
1004 => :focus_events,
# SGR Mouse Mode
1006 => :mouse_report_sgr,
# Use Alt Screen, Save/Restore State (no clear)
1047 => :dec_alt_screen_save,
# Save/Restore Cursor Position (and attributes)
1048 => :decsc_deccara,
# Use Alt Screen, Save/Restore State, Clear on switch
1049 => :alt_screen_buffer,
# Enable bracketed paste mode
2004 => :bracketed_paste
}
# Standard Mode codes and their corresponding mode atoms
@standard_modes %{
# Insert Mode
4 => :irm,
# Line Feed Mode
20 => :lnm,
# Column Width Mode
3 => :deccolm_132,
# 132 Column Mode
132 => :deccolm_132,
# 80 Column Mode
80 => :deccolm_80
}
defstruct cursor_visible: true,
auto_wrap: true,
origin_mode: false,
insert_mode: false,
line_feed_mode: false,
column_width_mode: :normal,
cursor_keys_mode: :normal,
screen_mode_reverse: false,
auto_repeat_mode: true,
interlacing_mode: false,
alternate_buffer_active: false,
mouse_report_mode: :none,
focus_events_enabled: false,
alt_screen_mode: nil,
bracketed_paste_mode: false,
active_buffer_type: :main
@type t :: %__MODULE__{}
@doc """
Creates a new mode state with default values.
"""
@spec new() :: t()
def new do
%__MODULE__{}
end
@doc """
Looks up a DEC private mode code and returns the corresponding mode atom.
"""
@spec lookup_private(integer()) :: atom() | nil
def lookup_private(code) when is_integer(code) do
Map.get(@dec_private_modes, code)
end
@doc """
Looks up a standard mode code and returns the corresponding mode atom.
"""
@spec lookup_standard(integer()) :: atom() | nil
def lookup_standard(code) when is_integer(code) do
Map.get(@standard_modes, code)
end
@doc """
Checks if a specific mode is enabled.
## Parameters
* `state` - The current mode state
* `mode` - The mode to check
## Returns
* `boolean()` - Whether the mode is enabled
"""
@spec mode_enabled?(t(), atom()) :: boolean()
def mode_enabled?(state, mode) do
case categorize_mode(mode) do
:basic -> check_basic_mode(state, mode)
:mouse -> check_mouse_mode(state, mode)
:column -> check_column_mode(state, mode)
:alt_screen -> check_alt_screen_mode(state, mode)
:decckm -> state.cursor_keys_mode == :application
:unknown -> false
end
end
defp categorize_mode(:decckm), do: :decckm
defp categorize_mode(mode)
when mode in [
:dectcem,
:decawm,
:decom,
:irm,
:lnm,
:decscnm,
:decarm,
:decinlm,
:focus_events,
:bracketed_paste
],
do: :basic
defp categorize_mode(mode)
when mode in [
:mouse_report_x10,
:mouse_report_cell_motion,
:mouse_report_sgr
],
do: :mouse
defp categorize_mode(mode) when mode in [:deccolm_80, :deccolm_132],
do: :column
defp categorize_mode(mode)
when mode in [:alt_screen_buffer, :dec_alt_screen, :dec_alt_screen_save],
do: :alt_screen
defp categorize_mode(_mode), do: :unknown
# Helper functions for mode categorization (currently unused but kept for future use)
# defp basic_mode?(mode) do
# mode in [
# :dectcem,
# :decawm,
# :decom,
# :irm,
# :lnm,
# :decscnm,
# :decarm,
# :decinlm,
# :focus_events,
# :bracketed_paste
# ]
# end
# defp mouse_mode?(mode) do
# mode in [:mouse_report_x10, :mouse_report_cell_motion, :mouse_report_sgr]
# end
# defp column_mode?(mode) do
# mode in [:deccolm_80, :deccolm_132]
# end
# defp alt_screen_mode?(mode) do
# mode in [:alt_screen_buffer, :dec_alt_screen, :dec_alt_screen_save]
# end
defp check_basic_mode(state, :dectcem), do: check_cursor_visible(state)
defp check_basic_mode(state, :decawm), do: check_auto_wrap(state)
defp check_basic_mode(state, :decom), do: check_origin_mode(state)
defp check_basic_mode(state, :irm), do: check_insert_mode(state)
defp check_basic_mode(state, :lnm), do: check_line_feed_mode(state)
defp check_basic_mode(state, :decscnm), do: check_screen_mode(state)
defp check_basic_mode(state, :decarm), do: check_auto_repeat_mode(state)
defp check_basic_mode(state, :decinlm), do: check_interlacing_mode(state)
defp check_basic_mode(state, :focus_events), do: check_focus_events(state)
defp check_basic_mode(state, :bracketed_paste),
do: check_bracketed_paste(state)
defp check_cursor_visible(state), do: state.cursor_visible
defp check_auto_wrap(state), do: state.auto_wrap
defp check_origin_mode(state), do: state.origin_mode
defp check_insert_mode(state), do: state.insert_mode
defp check_line_feed_mode(state), do: state.line_feed_mode
defp check_screen_mode(state), do: state.screen_mode_reverse
defp check_auto_repeat_mode(state), do: state.auto_repeat_mode
defp check_interlacing_mode(state), do: state.interlacing_mode
defp check_focus_events(state), do: state.focus_events_enabled
defp check_bracketed_paste(state), do: state.bracketed_paste_mode
defp check_mouse_mode(state, mode) do
case mode do
:mouse_report_x10 -> state.mouse_report_mode == :x10
:mouse_report_cell_motion -> state.mouse_report_mode == :cell_motion
:mouse_report_sgr -> state.mouse_report_mode == :sgr
end
end
defp check_column_mode(state, mode) do
case mode do
:deccolm_80 -> state.column_width_mode == :normal
:deccolm_132 -> state.column_width_mode == :wide
end
end
defp check_alt_screen_mode(state, _mode) do
state.alternate_buffer_active
end
@doc """
Sets a mode to enabled state.
## Parameters
* `state` - The current mode state
* `mode` - The mode to enable
## Returns
* `t()` - The updated mode state
"""
@spec set_mode(t(), atom()) :: t()
def set_mode(state, mode) do
case categorize_mode(mode) do
:basic -> set_basic_mode(state, mode)
:mouse -> set_mouse_mode(state, mode)
:column -> set_column_mode(state, mode)
:decckm -> %{state | cursor_keys_mode: :application}
:unknown -> state
end
end
defp set_basic_mode(state, :dectcem), do: set_cursor_visible(state)
defp set_basic_mode(state, :decawm), do: set_auto_wrap(state)
defp set_basic_mode(state, :decom), do: set_origin_mode(state)
defp set_basic_mode(state, :irm), do: set_insert_mode(state)
defp set_basic_mode(state, :lnm), do: set_line_feed_mode(state)
defp set_basic_mode(state, :decscnm), do: set_screen_mode(state)
defp set_basic_mode(state, :decarm), do: set_auto_repeat_mode(state)
defp set_basic_mode(state, :decinlm), do: set_interlacing_mode(state)
defp set_basic_mode(state, :focus_events), do: set_focus_events(state)
defp set_basic_mode(state, :bracketed_paste), do: set_bracketed_paste(state)
defp set_cursor_visible(state), do: %{state | cursor_visible: true}
defp set_auto_wrap(state), do: %{state | auto_wrap: true}
defp set_origin_mode(state), do: %{state | origin_mode: true}
defp set_insert_mode(state), do: %{state | insert_mode: true}
defp set_line_feed_mode(state), do: %{state | line_feed_mode: true}
defp set_screen_mode(state), do: %{state | screen_mode_reverse: true}
defp set_auto_repeat_mode(state), do: %{state | auto_repeat_mode: true}
defp set_interlacing_mode(state), do: %{state | interlacing_mode: true}
defp set_focus_events(state), do: %{state | focus_events_enabled: true}
defp set_bracketed_paste(state), do: %{state | bracketed_paste_mode: true}
defp set_mouse_mode(state, mode) do
case mode do
:mouse_report_x10 -> %{state | mouse_report_mode: :x10}
:mouse_report_cell_motion -> %{state | mouse_report_mode: :cell_motion}
:mouse_report_sgr -> %{state | mouse_report_mode: :sgr}
end
end
defp set_column_mode(state, mode) do
case mode do
:deccolm_132 -> %{state | column_width_mode: :wide}
:deccolm_80 -> %{state | column_width_mode: :normal}
end
end
@doc """
Resets a mode to disabled state.
## Parameters
* `state` - The current mode state
* `mode` - The mode to disable
## Returns
* `t()` - The updated mode state
"""
@spec reset_mode(t(), atom()) :: t()
def reset_mode(state, mode) do
case categorize_mode(mode) do
:basic -> reset_basic_mode(state, mode)
:mouse -> reset_mouse_mode(state)
:column -> reset_column_mode(state)
:decckm -> %{state | cursor_keys_mode: :normal}
:unknown -> state
end
end
defp reset_basic_mode(state, :dectcem), do: reset_cursor_visible(state)
defp reset_basic_mode(state, :decawm), do: reset_auto_wrap(state)
defp reset_basic_mode(state, :decom), do: reset_origin_mode(state)
defp reset_basic_mode(state, :irm), do: reset_insert_mode(state)
defp reset_basic_mode(state, :lnm), do: reset_line_feed_mode(state)
defp reset_basic_mode(state, :decscnm), do: reset_screen_mode(state)
defp reset_basic_mode(state, :decarm), do: reset_auto_repeat_mode(state)
defp reset_basic_mode(state, :decinlm), do: reset_interlacing_mode(state)
defp reset_basic_mode(state, :focus_events), do: reset_focus_events(state)
defp reset_basic_mode(state, :bracketed_paste),
do: reset_bracketed_paste(state)
defp reset_cursor_visible(state), do: %{state | cursor_visible: false}
defp reset_auto_wrap(state), do: %{state | auto_wrap: false}
defp reset_origin_mode(state), do: %{state | origin_mode: false}
defp reset_insert_mode(state), do: %{state | insert_mode: false}
defp reset_line_feed_mode(state), do: %{state | line_feed_mode: false}
defp reset_screen_mode(state), do: %{state | screen_mode_reverse: false}
defp reset_auto_repeat_mode(state), do: %{state | auto_repeat_mode: false}
defp reset_interlacing_mode(state), do: %{state | interlacing_mode: false}
defp reset_focus_events(state), do: %{state | focus_events_enabled: false}
defp reset_bracketed_paste(state), do: %{state | bracketed_paste_mode: false}
defp reset_mouse_mode(state), do: %{state | mouse_report_mode: :none}
defp reset_column_mode(state), do: %{state | column_width_mode: :normal}
@doc """
Sets the alternate buffer mode.
## Parameters
* `state` - The current mode state
* `type` - The alternate buffer mode type
## Returns
* `t()` - The updated mode state
"""
@spec set_alternate_buffer_mode(t(), atom()) :: t()
def set_alternate_buffer_mode(state, type) do
%{state | alternate_buffer_active: true, alt_screen_mode: type}
end
@doc """
Resets the alternate buffer mode.
## Parameters
* `state` - The current mode state
## Returns
* `t()` - The updated mode state
"""
@spec reset_alternate_buffer_mode(t()) :: t()
def reset_alternate_buffer_mode(state) do
%{state | alternate_buffer_active: false, alt_screen_mode: nil}
end
end