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

lib/raxol/terminal/emulator.ex

defmodule Raxol.Terminal.Emulator do
@moduledoc """
Enterprise-grade terminal emulator with VT100/ANSI support and high-performance parsing.
Provides full terminal emulation with true color, mouse tracking, alternate screen,
and modern features. Uses modular architecture with separate coordinators for
buffer, mode, input, and output operations.
## Usage
# Create standard emulator
emulator = Raxol.Terminal.Emulator.new(80, 24)
# Process input with colors
{emulator, output} = Raxol.Terminal.Emulator.process_input(
emulator,
"\\e[1;31mRed Bold\\e[0m Normal text"
)
## Performance Modes
* `new/2` - Full features (2.8MB, ~95ms startup)
* `new_lite/3` - Most features (1.2MB, ~30ms startup)
* `new_minimal/2` - Basic only (8.8KB, <10ms startup)
"""
alias Raxol.Terminal.Emulator.BufferOperations
alias Raxol.Terminal.Emulator.Coordinator
alias Raxol.Terminal.Emulator.CursorOps
alias Raxol.Terminal.Emulator.Factory
alias Raxol.Terminal.Emulator.InputProcessing
alias Raxol.Terminal.Emulator.ModeOperations
@compile {:no_warn_undefined,
[
Raxol.Terminal.Emulator.BufferOperations,
Raxol.Terminal.Emulator.CursorOps,
Raxol.Terminal.Emulator.Factory,
Raxol.Terminal.Emulator.InputProcessing
]}
@behaviour Raxol.Terminal.EmulatorBehaviour
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
@default_scrollback Raxol.Core.Defaults.scrollback_limit()
# Terminal emulator state requires many fields (modes, buffers, managers, cursor, etc.)
# credo:disable-for-next-line Credo.Check.Warning.StructFieldAmount
defstruct state: nil,
event: nil,
buffer: nil,
config: nil,
command: nil,
cursor: nil,
window_manager: nil,
mode_manager: nil,
active_buffer_type: :main,
main_screen_buffer: nil,
active: nil,
alternate: nil,
charset_state: %{
g0: :us_ascii,
g1: :us_ascii,
g2: :us_ascii,
g3: :us_ascii,
gl: :g0,
gr: :g0,
single_shift: nil,
active: :us_ascii
},
width: @default_width,
height: @default_height,
window_state: %{
iconified: false,
maximized: false,
position: {0, 0},
size: {@default_width, @default_height},
size_pixels: {@default_width * 8, @default_height * 16},
stacking_order: :normal,
previous_size: {@default_width, @default_height},
saved_size: {@default_width, @default_height},
icon_name: ""
},
state_stack: [],
parser_state: %Raxol.Terminal.Parser.ParserState{state: :ground},
command_history: [],
max_command_history: 100,
history_buffer: nil,
scrollback_buffer: [],
output_buffer: [],
current_command_buffer: "",
screen_buffer_manager: nil,
output_manager: nil,
cursor_manager: nil,
scrollback_manager: nil,
selection_manager: nil,
mode_manager_pid: nil,
style_manager: nil,
damage_tracker: nil,
mode_state: %{},
style: nil,
cursor_style: :block,
bracketed_paste_active: false,
bracketed_paste_buffer: "",
saved_cursor: nil,
scroll_region: {0, @default_height - 1},
scrollback_limit: @default_scrollback,
memory_limit: 10_000_000,
session_id: "",
client_options: %{},
window_title: nil,
last_col_exceeded: false,
icon_name: nil,
tab_stops: [],
color_palette: %{},
last_key_event: nil,
current_hyperlink: nil,
active_buffer: nil,
alternate_screen_buffer: nil,
sixel_state: nil,
cursor_blink_rate: 500,
device_status_reported: false,
cursor_position_reported: false,
notification_manager: nil,
clipboard_manager: nil,
hyperlink_manager: nil,
font_manager: nil,
color_manager: nil,
capabilities_manager: nil,
device_status_manager: nil,
graphics_manager: nil,
input_manager: nil,
metrics_manager: nil,
mouse_manager: nil,
plugin_manager: nil,
registry: nil,
renderer: nil,
scroll_manager: nil,
session_manager: nil,
state_manager: nil,
supervisor: nil,
sync_manager: nil,
tab_manager: nil,
terminal_state_manager: nil,
theme_manager: nil,
validation_service: nil,
window_registry: nil
@type t :: %__MODULE__{
state: any(),
event: any(),
buffer: any(),
config: any(),
command: any(),
cursor: any(),
window_manager: any(),
mode_manager: any(),
active_buffer_type: atom(),
main_screen_buffer: any(),
active: any(),
alternate: any(),
charset_state: map(),
width: non_neg_integer(),
height: non_neg_integer(),
window_state: map(),
state_stack: list(),
parser_state: any(),
command_history: list(),
max_command_history: non_neg_integer(),
scrollback_buffer: list(),
output_buffer: list(),
current_command_buffer: String.t(),
screen_buffer_manager: any(),
output_manager: any(),
cursor_manager: any(),
scrollback_manager: any(),
selection_manager: any(),
mode_manager_pid: any(),
style_manager: any(),
damage_tracker: any(),
mode_state: map(),
style: any(),
cursor_style: atom(),
bracketed_paste_active: boolean(),
bracketed_paste_buffer: String.t(),
saved_cursor: any(),
scroll_region: tuple(),
scrollback_limit: non_neg_integer(),
memory_limit: non_neg_integer(),
session_id: String.t(),
client_options: map(),
window_title: String.t() | nil,
last_col_exceeded: boolean(),
icon_name: String.t() | nil,
tab_stops: list(),
color_palette: map(),
last_key_event: any(),
current_hyperlink: any(),
active_buffer: any(),
alternate_screen_buffer: any(),
sixel_state: any(),
cursor_blink_rate: non_neg_integer(),
device_status_reported: boolean(),
cursor_position_reported: boolean(),
notification_manager: any(),
clipboard_manager: any(),
hyperlink_manager: any(),
font_manager: any(),
color_manager: any(),
capabilities_manager: any(),
device_status_manager: any(),
graphics_manager: any(),
input_manager: any(),
metrics_manager: any(),
mouse_manager: any(),
plugin_manager: any(),
registry: any(),
renderer: any(),
scroll_manager: any(),
session_manager: any(),
state_manager: any(),
supervisor: any(),
sync_manager: any(),
tab_manager: any(),
terminal_state_manager: any(),
theme_manager: any(),
validation_service: any(),
window_registry: any()
}
# Cursor operations - delegated to CursorOps
@doc "Gets the current cursor position as {x, y}."
@impl Raxol.Terminal.EmulatorBehaviour
defdelegate get_cursor_position(emulator), to: CursorOps
@doc "Sets the cursor position to the specified coordinates."
defdelegate set_cursor_position(emulator, x, y), to: CursorOps
@doc "Gets the current cursor style (:block, :line, :underscore)."
defdelegate get_cursor_style(emulator), to: CursorOps
@doc "Sets the cursor style to :block, :line, or :underscore."
defdelegate set_cursor_style(emulator, style), to: CursorOps
@doc "Returns true if the cursor is visible."
defdelegate cursor_visible?(emulator), to: CursorOps
@doc "Gets cursor visibility state."
@impl Raxol.Terminal.EmulatorBehaviour
defdelegate get_cursor_visible(emulator), to: CursorOps
@doc "Gets cursor position as a structured object."
defdelegate get_cursor_position_struct(emulator), to: CursorOps
@doc "Gets cursor visibility from mode manager."
defdelegate get_mode_manager_cursor_visible(emulator), to: CursorOps
@doc "Sets cursor visibility."
defdelegate set_cursor_visibility(emulator, visible), to: CursorOps
@doc "Returns true if the cursor is blinking."
defdelegate cursor_blinking?(emulator), to: CursorOps
@doc "Sets cursor blinking state."
defdelegate set_cursor_blink(emulator, blinking), to: CursorOps
@doc "Returns cursor blinking state."
defdelegate blinking?(emulator), to: CursorOps
@doc "Returns cursor visibility state."
defdelegate visible?(emulator), to: CursorOps
# Erase operations - delegated to Operations.ScreenOperations
@doc "Clears the entire screen."
defdelegate clear_screen(emulator), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Clears the specified line."
defdelegate clear_line(emulator, line), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases display content based on mode (0=to end, 1=from start, 2=entire)."
defdelegate erase_display(emulator, mode), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases content within the display."
defdelegate erase_in_display(emulator, mode), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases line content based on mode."
defdelegate erase_line(emulator, mode), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases content within the current line."
defdelegate erase_in_line(emulator, mode), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases from cursor position to end of screen."
defdelegate erase_from_cursor_to_end(emulator), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases from start of screen to cursor position."
defdelegate erase_from_start_to_cursor(emulator), to: Raxol.Terminal.Operations.ScreenOperations
@doc "Erases the specified number of characters."
defdelegate erase_chars(emulator, count), to: Raxol.Terminal.Operations.ScreenOperations
# Text operations - delegated to Operations.TextOperations
@doc "Inserts a character at the cursor position."
defdelegate insert_char(emulator, char), to: Raxol.Terminal.Operations.TextOperations
@doc "Inserts the specified number of blank characters."
defdelegate insert_chars(emulator, count), to: Raxol.Terminal.Operations.TextOperations
@doc "Deletes the character at the cursor position."
defdelegate delete_char(emulator), to: Raxol.Terminal.Operations.TextOperations
@doc "Deletes the specified number of characters."
defdelegate delete_chars(emulator, count), to: Raxol.Terminal.Operations.TextOperations
@doc "Writes text to the terminal at the cursor position."
defdelegate write_text(emulator, text), to: Raxol.Terminal.Operations.TextOperations
# Selection operations
@doc "Starts text selection at the specified coordinates."
defdelegate start_selection(emulator, x, y),
to: Raxol.Terminal.Operations.SelectionOperations
@doc "Updates the selection endpoint to the specified coordinates."
defdelegate update_selection(emulator, x, y),
to: Raxol.Terminal.Operations.SelectionOperations
@doc "Ends the current text selection."
defdelegate end_selection(emulator),
to: Raxol.Terminal.Operations.SelectionOperations
@doc "Clears the current text selection."
defdelegate clear_selection(emulator),
to: Raxol.Terminal.Operations.SelectionOperations
@doc "Gets the currently selected text."
defdelegate get_selection(emulator),
to: Raxol.Terminal.Operations.SelectionOperations
@doc "Returns true if text is currently selected."
defdelegate has_selection?(emulator),
to: Raxol.Terminal.Operations.SelectionOperations
# Scroll operations
@doc "Scrolls the display up by the specified number of lines."
defdelegate scroll_up(emulator, lines),
to: Raxol.Terminal.Operations.ScrollOperations
@doc "Scrolls the display down by the specified number of lines."
defdelegate scroll_down(emulator, lines),
to: Raxol.Terminal.Operations.ScrollOperations
# State operations
@doc "Saves the current terminal state."
defdelegate save_state(emulator),
to: Raxol.Terminal.Operations.StateOperations
@doc "Restores the previously saved terminal state."
defdelegate restore_state(emulator),
to: Raxol.Terminal.Operations.StateOperations
# Buffer operations - delegated to BufferOperations
@doc "Switches to the alternate screen buffer."
defdelegate switch_to_alternate_screen(emulator), to: BufferOperations
@doc "Switches to the normal screen buffer."
defdelegate switch_to_normal_screen(emulator), to: BufferOperations
@doc "Clears the scrollback buffer."
defdelegate clear_scrollback(emulator), to: BufferOperations
@doc "Updates the active buffer with new content."
@impl Raxol.Terminal.EmulatorBehaviour
defdelegate update_active_buffer(emulator, buffer), to: BufferOperations
@doc "Writes data to the output buffer."
defdelegate write_to_output(emulator, data), to: BufferOperations
# Dimension operations
@doc "Gets the terminal width in columns."
defdelegate get_width(emulator),
to: Raxol.Terminal.Emulator.Dimensions
@doc "Gets the terminal height in rows."
defdelegate get_height(emulator),
to: Raxol.Terminal.Emulator.Dimensions
@doc "Gets the current scroll region as {top, bottom}."
defdelegate get_scroll_region(emulator),
to: Raxol.Terminal.Emulator.Dimensions
# Process management
@doc """
Starts a linked terminal emulator process.
"""
def start_link(opts \\ []) do
width = Keyword.get(opts, :width, @default_width)
height = Keyword.get(opts, :height, @default_height)
name = Keyword.get(opts, :name)
initial_state = new(width, height, opts)
if Code.ensure_loaded?(__MODULE__.Server) do
GenServer.start_link(
__MODULE__.Server,
{initial_state, opts},
if(name, do: [name: name], else: [])
)
else
{:ok, spawn(fn -> :ok end)}
end
end
# Constructor functions
@impl Raxol.Terminal.EmulatorBehaviour
def new do
new(@default_width, @default_height, [])
end
@impl Raxol.Terminal.EmulatorBehaviour
def new(width, height) do
new(width, height, [])
end
@impl Raxol.Terminal.EmulatorBehaviour
def new(width, height, opts) do
if Keyword.get(opts, :use_genservers, false) do
Factory.create_full(width, height, opts)
else
Factory.create_basic(width, height, opts)
end
end
@impl Raxol.Terminal.EmulatorBehaviour
def new(width, height, session_id, client_options) do
opts = [session_id: session_id, client_options: client_options]
{:ok, new(width, height, opts)}
end
@deprecated "Use new/3 with use_genservers: false option instead"
def new_lite(width \\ @default_width, height \\ @default_height, opts \\ []) do
new(width, height, Keyword.put(opts, :use_genservers, false))
end
@deprecated "Use new/3 with enable_history: false, alternate_buffer: false options instead"
def new_minimal(width \\ @default_width, height \\ @default_height) do
new(width, height,
enable_history: false,
alternate_buffer: false,
use_genservers: false
)
end
# Reset and resize
@doc "Resets the terminal emulator to its initial state."
def reset(emulator), do: Coordinator.reset(emulator)
@doc "Resizes the terminal to the specified dimensions."
@impl Raxol.Terminal.EmulatorBehaviour
def resize(emulator, new_width, new_height) do
Coordinator.resize(emulator, new_width, new_height)
end
# Complex coordination operations
@doc "Moves the cursor to the specified position."
def move_cursor(emulator, x, y), do: Coordinator.move_cursor(emulator, x, y)
@doc "Clears the screen and moves cursor to home position."
def clear_screen_and_home(emulator),
do: Coordinator.clear_screen_and_home(emulator)
@doc "Validates terminal dimensions."
def validate_dimensions(width, height),
do: Coordinator.validate_dimensions(width, height)
# Mode update functions
@doc "Updates insert mode state."
def update_insert_mode(emulator, enabled) do
mode_state = Map.put(emulator.mode_state, :insert_mode, enabled)
{:ok, %{emulator | mode_state: mode_state}}
end
@doc "Updates auto wrap mode state."
def update_auto_wrap_mode(emulator, enabled) do
mode_state = Map.put(emulator.mode_state, :auto_wrap, enabled)
{:ok, %{emulator | mode_state: mode_state}}
end
# Screen buffer accessor
@doc "Gets the active screen buffer."
@impl Raxol.Terminal.EmulatorBehaviour
def get_screen_buffer(emulator), do: BufferOperations.get_screen_buffer(emulator)
@doc "Sets terminal dimensions after validation."
def set_dimensions(emulator, width, height) do
case Coordinator.validate_dimensions(width, height) do
{:ok, _} -> {:ok, %{emulator | width: width, height: height}}
error -> error
end
end
# Legacy compatibility functions
@doc "Gets output buffer (legacy compatibility)."
def get_output_buffer(_emulator), do: {:ok, []}
@doc "Applies color changes (legacy compatibility)."
def apply_color_changes(emulator), do: {:ok, emulator}
@doc "Updates blink state (legacy compatibility)."
def update_blink_state(emulator), do: {:ok, emulator}
@doc "Processes input and returns updated emulator with output."
@impl Raxol.Terminal.EmulatorBehaviour
defdelegate process_input(emulator, input), to: InputProcessing
# Additional functions needed by various modules
@doc "Gets the scrollback buffer contents."
def get_scrollback(emulator), do: emulator.scrollback_buffer || []
@doc "Performs automatic scrolling if needed."
def maybe_scroll(emulator), do: emulator
@doc "Sets a terminal mode."
def set_mode(emulator, mode), do: ModeOperations.set_mode(emulator, mode)
@doc "Resets a terminal mode."
def reset_mode(emulator, mode), do: ModeOperations.reset_mode(emulator, mode)
@doc "Sets a terminal attribute."
def set_attribute(emulator, _attr, _value), do: emulator
@doc "Gets the mode manager."
def get_mode_manager(emulator), do: emulator.mode_manager
@doc "Gets the configuration structure."
def get_config_struct(emulator),
do: Raxol.Terminal.Emulator.Helpers.get_config_struct(emulator)
@doc "Moves cursor to specified position."
def move_cursor_to(emulator, x, y), do: move_cursor(emulator, x, y)
@doc "Moves cursor to specified position with options."
def move_cursor_to(emulator, x, y, _opts), do: move_cursor(emulator, x, y)
@doc "Moves cursor up (stub implementation)."
def move_cursor_up(emulator, _count), do: emulator
@doc "Moves cursor down (stub implementation)."
def move_cursor_down(emulator, _count), do: emulator
@doc "Moves cursor forward (stub implementation)."
def move_cursor_forward(emulator, _count), do: emulator
@doc "Moves cursor back (stub implementation)."
def move_cursor_back(emulator, _count), do: emulator
@doc "Handles ESC = sequence (DECKPAM - Enable application keypad mode)."
defdelegate handle_esc_equals(emulator), to: InputProcessing
@doc "Handles ESC > sequence (DECKPNM - Disable application keypad mode)."
defdelegate handle_esc_greater(emulator), to: InputProcessing
@doc "Gets output from the emulator."
@spec get_output(t()) :: String.t()
def get_output(_emulator), do: ""
@doc "Renders the emulator screen."
@spec render_screen(t()) :: String.t()
def render_screen(emulator), do: get_output(emulator)
@doc "Cleans up emulator resources."
@spec cleanup(t()) :: :ok
def cleanup(_emulator), do: :ok
end