Packages
Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/modes.ex
defmodule Raxol.Terminal.Modes do
@moduledoc """
Handles terminal modes and state transitions for the terminal emulator.
This module provides functions for managing terminal modes, processing
escape sequences, and handling terminal state transitions.
"""
@type mode :: :insert | :replace | :visual | :command | :normal
@type mode_state :: %{mode => boolean()}
@doc """
Creates a new terminal mode state.
## Examples
iex> modes = Modes.new()
iex> modes.insert
false
"""
def new do
%{
insert: false,
replace: true,
visual: false,
command: false,
normal: true
}
end
@doc """
Sets a terminal mode.
## Examples
iex> modes = Modes.new()
iex> modes = Modes.set_mode(modes, :insert)
iex> modes.insert
true
iex> modes.replace
false
"""
def set_mode(%{} = modes, mode) when is_atom(mode) do
# Turn off all modes first
modes =
Enum.reduce(modes, %{}, fn {k, _}, acc -> Map.put(acc, k, false) end)
# Then set the requested mode
Map.put(modes, mode, true)
end
@doc """
Resets a specific terminal mode to its default value.
## Examples
iex> modes = Modes.new() |> Modes.set_mode(:insert)
iex> modes.insert
true
iex> modes = Modes.reset_mode(modes, :insert)
iex> modes.insert
false
iex> modes = Modes.reset_mode(modes, :replace) # replace defaults to true
iex> modes.replace
true
"""
@spec reset_mode(mode_state(), atom()) :: mode_state()
def reset_mode(%{} = modes, mode) when is_atom(mode) do
default_modes = new()
# Default to false if mode unknown
default_value = Map.get(default_modes, mode, false)
Map.put(modes, mode, default_value)
end
@doc """
Checks if a terminal mode is active.
## Examples
iex> modes = Modes.new()
iex> Modes.active?(modes, :normal)
true
iex> Modes.active?(modes, :insert)
false
"""
def active?(%{} = modes, mode) when is_atom(mode) do
Map.get(modes, mode, false)
end
@doc """
Processes an escape sequence for terminal mode changes.
## Examples
iex> modes = Modes.new()
iex> {modes, _} = Modes.process_escape(modes, "?1049h")
iex> Modes.active?(modes, :alternate_screen)
true
"""
def process_escape(%{} = modes, "?1049h"),
do: handle_alternate_screen(modes, "?1049h")
def process_escape(%{} = modes, "?1049l"),
do: handle_alternate_screen(modes, "?1049l")
def process_escape(%{} = modes, "?7h"), do: handle_line_wrap(modes, "?7h")
def process_escape(%{} = modes, "?7l"), do: handle_line_wrap(modes, "?7l")
def process_escape(%{} = modes, "?8h"), do: handle_auto_repeat(modes, "?8h")
def process_escape(%{} = modes, "?8l"), do: handle_auto_repeat(modes, "?8l")
def process_escape(%{} = modes, "?25h"),
do: handle_cursor_visibility(modes, "?25h")
def process_escape(%{} = modes, "?25l"),
do: handle_cursor_visibility(modes, "?25l")
def process_escape(%{} = modes, "4h"), do: handle_insert_mode(modes, "4h")
def process_escape(%{} = modes, "4l"), do: handle_insert_mode(modes, "4l")
def process_escape(%{} = modes, "?1000h"),
do: handle_visual_mode(modes, "?1000h")
def process_escape(%{} = modes, "?1000l"),
do: handle_visual_mode(modes, "?1000l")
def process_escape(%{} = modes, "?1001h"),
do: handle_command_mode(modes, "?1001h")
def process_escape(%{} = modes, "?1001l"),
do: handle_command_mode(modes, "?1001l")
def process_escape(%{} = modes, "?1002h"),
do: handle_normal_mode(modes, "?1002h")
def process_escape(%{} = modes, "?1002l"),
do: handle_normal_mode(modes, "?1002l")
def process_escape(%{} = modes, sequence),
do: {modes, "Unknown escape sequence: #{sequence}"}
defp handle_alternate_screen(modes, "?1049h"),
do: {Map.put(modes, :alternate_screen, true), "Switched to alternate screen buffer"}
defp handle_alternate_screen(modes, "?1049l"),
do: {Map.put(modes, :alternate_screen, false), "Switched to main screen buffer"}
defp handle_line_wrap(modes, "?7h"),
do: {Map.put(modes, :line_wrap, true), "Line wrapping enabled"}
defp handle_line_wrap(modes, "?7l"),
do: {Map.put(modes, :line_wrap, false), "Line wrapping disabled"}
defp handle_auto_repeat(modes, "?8h"),
do: {Map.put(modes, :auto_repeat, true), "Auto-repeat enabled"}
defp handle_auto_repeat(modes, "?8l"),
do: {Map.put(modes, :auto_repeat, false), "Auto-repeat disabled"}
defp handle_cursor_visibility(modes, "?25h"),
do: {Map.put(modes, :cursor_visible, true), "Cursor visible"}
defp handle_cursor_visibility(modes, "?25l"),
do: {Map.put(modes, :cursor_visible, false), "Cursor hidden"}
defp handle_insert_mode(modes, "4h"),
do: {set_mode(modes, :insert), "Insert mode enabled"}
defp handle_insert_mode(modes, "4l"),
do: {set_mode(modes, :replace), "Replace mode enabled"}
defp handle_visual_mode(modes, "?1000h"),
do: {Map.put(modes, :visual, true), "Visual mode enabled"}
defp handle_visual_mode(modes, "?1000l"),
do: {Map.put(modes, :visual, false), "Visual mode disabled"}
defp handle_command_mode(modes, "?1001h"),
do: {Map.put(modes, :command, true), "Command mode enabled"}
defp handle_command_mode(modes, "?1001l"),
do: {Map.put(modes, :command, false), "Command mode disabled"}
defp handle_normal_mode(modes, "?1002h"),
do: {set_mode(modes, :normal), "Normal mode enabled"}
defp handle_normal_mode(modes, "?1002l"),
do: {set_mode(modes, :normal), "Normal mode disabled"}
@doc """
Saves the current terminal mode state.
## Examples
iex> modes = Modes.new()
iex> {modes, saved_modes} = Modes.save_state(modes)
iex> modes = Modes.set_mode(modes, :insert)
iex> modes = Modes.restore_state(modes, saved_modes)
iex> Modes.active?(modes, :normal)
true
"""
def save_state(%{} = modes) do
# Maps are immutable, just return the current map
{modes, modes}
end
@doc """
Restores a previously saved terminal mode state.
## Examples
iex> modes = Modes.new()
iex> {modes, saved_modes} = Modes.save_state(modes)
iex> modes = Modes.set_mode(modes, :insert)
iex> modes = Modes.restore_state(modes, saved_modes)
iex> Modes.active?(modes, :normal)
true
"""
def restore_state(%{} = _modes, %{} = saved_modes) do
saved_modes
end
@doc """
Returns a list of all active terminal modes.
## Examples
iex> modes = Modes.new()
iex> Modes.active_modes(modes)
[:normal, :replace]
"""
def active_modes(%{} = modes) do
modes
|> Enum.filter(fn {_k, v} -> v end)
|> Enum.map(fn {k, _v} -> k end)
end
@doc """
Returns a string representation of the terminal mode state.
## Examples
iex> modes = Modes.new()
iex> Modes.to_string(modes)
"Terminal Modes: normal, replace"
"""
def to_string(%{} = modes) do
active = active_modes(modes)
"Terminal Modes: #{Enum.join(active, ", ")}"
end
end