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/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