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/constructors.ex
defmodule Raxol.Terminal.Emulator.Constructors do
@moduledoc """
Handles emulator constructor functions.
This module extracts the constructor logic from the main emulator.
"""
alias Raxol.Terminal.{Cursor.Manager, ModeManager, ScreenBuffer}
alias Raxol.Terminal.ScreenBufferAdapter, as: ScreenBuffer
@default_width Raxol.Core.Defaults.terminal_width()
@default_height Raxol.Core.Defaults.terminal_height()
@default_scrollback Raxol.Core.Defaults.scrollback_limit()
@doc """
Creates a new terminal emulator instance with default dimensions.
"""
def new do
new(@default_width, @default_height)
end
@doc """
Creates a new terminal emulator instance with given width and height.
"""
def new(width, height) do
# Initialize all required managers and processes
event_pid = get_pid(Raxol.Terminal.Event.Handler.start_link([]))
buffer_pid =
get_pid(
Raxol.Terminal.ScreenBuffer.Manager.start_link(
width: width,
height: height
)
)
config_pid =
get_pid(Raxol.Terminal.Config.Manager.start_link(width: width, height: height))
command_pid = get_pid(Raxol.Terminal.Commands.Manager.start_link([]))
cursor_manager_pid = get_pid(Manager.start_link([]))
window_manager_pid = get_pid(Raxol.Terminal.Window.Manager.start_link([]))
mode_manager = ModeManager.new()
# Initialize screen buffers
main_buffer = ScreenBuffer.new(width, height)
alternate_buffer = ScreenBuffer.new(width, height)
# Create a proper CursorManager struct
cursor = %Manager{
row: 0,
col: 0,
position: {0, 0},
visible: true,
blinking: true,
style: :block,
bottom_margin: height - 1
}
%Raxol.Terminal.Emulator{
# Core managers
state: %{modes: %{}, attributes: %{}, state_stack: []},
event: event_pid,
buffer: buffer_pid,
config: config_pid,
command: command_pid,
cursor: cursor,
cursor_manager: cursor_manager_pid,
window_manager: window_manager_pid,
mode_manager: mode_manager,
# Screen buffers
active_buffer_type: :main,
main_screen_buffer: main_buffer,
alternate_screen_buffer: alternate_buffer,
# Character set state
charset_state: %{
g0: :us_ascii,
g1: :us_ascii,
g2: :us_ascii,
g3: :us_ascii,
gl: :g0,
gr: :g0,
active: :g0,
single_shift: nil
},
# Dimensions
width: width,
height: height,
# Window state
window_state: %{
iconified: false,
maximized: false,
position: {0, 0},
size: {width, height},
size_pixels: {width * 8, height * 16},
stacking_order: :normal,
previous_size: {width, height},
saved_size: {width, height},
icon_name: ""
},
# State stack for terminal state management
state_stack: [],
# Command history
command_history: [],
current_command_buffer: "",
max_command_history: 100,
history_buffer: Raxol.Terminal.HistoryBuffer.new(),
# Other fields
output_buffer: "",
style: Raxol.Terminal.ANSI.TextFormatting.new(),
scrollback_limit: @default_scrollback,
scrollback_buffer: [],
window_title: nil,
plugin_manager: nil,
saved_cursor: nil,
scroll_region: nil,
sixel_state: nil,
last_col_exceeded: false,
cursor_blink_rate: 0,
cursor_style: :block,
session_id: nil,
client_options: %{}
}
end
@doc """
Creates a new terminal emulator instance with given width, height, and options.
"""
def new(width, height, opts) do
event_pid = get_pid(Raxol.Terminal.Event.Handler.start_link(opts))
buffer_pid =
get_pid(
Raxol.Terminal.ScreenBuffer.Manager.start_link([width: width, height: height] ++ opts)
)
config_pid =
get_pid(Raxol.Terminal.Config.Manager.start_link([width: width, height: height] ++ opts))
command_pid = get_pid(Raxol.Terminal.Commands.Manager.start_link(opts))
cursor_manager_pid = get_pid(Manager.start_link(opts))
window_manager_pid = get_pid(Raxol.Terminal.Window.Manager.start_link(opts))
mode_manager = ModeManager.new()
# Initialize screen buffers
main_buffer = ScreenBuffer.new(width, height)
alternate_buffer = ScreenBuffer.new(width, height)
# Create a proper CursorManager struct
cursor = %Manager{
row: 0,
col: 0,
position: {0, 0},
visible: true,
blinking: true,
style: :block,
bottom_margin: height - 1
}
# Get plugin manager from options
plugin_manager = Keyword.get(opts, :plugin_manager)
%Raxol.Terminal.Emulator{
# Core managers
state: %{modes: %{}, attributes: %{}, state_stack: []},
event: event_pid,
buffer: buffer_pid,
config: config_pid,
command: command_pid,
cursor: cursor,
cursor_manager: cursor_manager_pid,
window_manager: window_manager_pid,
mode_manager: mode_manager,
# Screen buffers
active_buffer_type: :main,
main_screen_buffer: main_buffer,
alternate_screen_buffer: alternate_buffer,
# Character set state
charset_state: %{
g0: :us_ascii,
g1: :us_ascii,
g2: :us_ascii,
g3: :us_ascii,
gl: :g0,
gr: :g0,
active: :g0,
single_shift: nil
},
# Dimensions
width: width,
height: height,
# Window state
window_state: %{
iconified: false,
maximized: false,
position: {0, 0},
size: {width, height},
size_pixels: {width * 8, height * 16},
stacking_order: :normal,
previous_size: {width, height},
saved_size: {width, height},
icon_name: ""
},
# State stack for terminal state management
state_stack: [],
# Command history
command_history: [],
current_command_buffer: "",
max_command_history: Keyword.get(opts, :max_command_history, 100),
history_buffer:
Raxol.Terminal.HistoryBuffer.new(
Keyword.get(opts, :max_command_history, @default_scrollback)
),
# Other fields
output_buffer: "",
style: Raxol.Terminal.ANSI.TextFormatting.new(),
scrollback_limit: Keyword.get(opts, :scrollback_limit, @default_scrollback),
scrollback_buffer: [],
window_title: nil,
plugin_manager: plugin_manager,
saved_cursor: nil,
scroll_region: nil,
sixel_state: nil,
last_col_exceeded: false,
cursor_blink_rate: 0,
cursor_style: :block,
session_id: nil,
client_options: %{}
}
end
@doc """
Creates a new terminal emulator instance with options map.
"""
def new(%{width: width, height: height} = opts) do
plugin_manager = Map.get(opts, :plugin_manager)
emulator = new(width, height, [])
case plugin_manager do
nil -> emulator
_ -> %{emulator | plugin_manager: plugin_manager}
end
end
@doc """
Creates a new emulator with width, height, and optional configuration.
"""
def new(width, height, config, options) do
# Merge config and options
merged_opts = Map.merge(config, options)
# Convert to keyword list for existing new/3 function
opts_list = Map.to_list(merged_opts)
# Create emulator using existing constructor
emulator = new(width, height, opts_list)
# Apply any additional configuration
emulator = apply_additional_config(emulator, merged_opts)
emulator
end
# Private functions
# Unlink child processes so caller isn't killed by child crashes.
# Emulator children are managed by the Emulator struct, not a supervisor.
defp get_pid({:ok, pid}) do
Process.unlink(pid)
pid
end
defp get_pid({:error, {:already_started, pid}}), do: pid
defp get_pid({:error, reason}),
do: raise("Failed to start process: #{inspect(reason)}")
defp apply_additional_config(emulator, config) do
emulator
|> maybe_set_plugin_manager(config)
|> maybe_set_session_id(config)
|> maybe_set_client_options(config)
end
defp maybe_set_plugin_manager(emulator, %{plugin_manager: plugin_manager}) do
%{emulator | plugin_manager: plugin_manager}
end
defp maybe_set_plugin_manager(emulator, _), do: emulator
defp maybe_set_session_id(emulator, %{session_id: session_id}) do
%{emulator | session_id: session_id}
end
defp maybe_set_session_id(emulator, _), do: emulator
defp maybe_set_client_options(emulator, %{client_options: client_options}) do
%{emulator | client_options: client_options}
end
defp maybe_set_client_options(emulator, _), do: emulator
end