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