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/input/input_handler.ex
defmodule Raxol.Terminal.Input.InputHandler do
@moduledoc """
Handles input processing for the terminal emulator.
This module manages keyboard input, mouse events, input history,
and modifier key states.
"""
defstruct buffer: "",
mode: :normal,
mouse_enabled: false,
mouse_buttons: MapSet.new(),
mouse_position: {0, 0},
input_history: [],
history_index: nil,
modifier_state: %{
ctrl: false,
alt: false,
shift: false,
meta: false
}
@type t :: %__MODULE__{}
@doc """
Creates a new input handler with default values.
"""
def new do
%__MODULE__{}
end
@doc """
Processes regular keyboard input.
"""
def process_keyboard(%__MODULE__{} = handler, key) when is_binary(key) do
%{handler | buffer: handler.buffer <> key}
end
@doc """
Processes special keys like arrow keys, function keys, etc.
"""
def process_special_key(%__MODULE__{} = handler, key) do
sequence = Raxol.Terminal.Input.SpecialKeys.atom_to_escape_sequence(key)
%{handler | buffer: sequence}
end
@doc """
Updates modifier key state.
"""
def update_modifier(%__MODULE__{} = handler, modifier, state) do
modifier_key =
case modifier do
"Control" -> :ctrl
"Alt" -> :alt
"Shift" -> :shift
"Meta" -> :meta
_ -> :ctrl
end
modifier_state = Map.put(handler.modifier_state, modifier_key, state)
%{handler | modifier_state: modifier_state}
end
@doc """
Processes key with current modifier state.
"""
def process_key_with_modifiers(%__MODULE__{} = handler, key) do
sequence =
Raxol.Terminal.Input.SpecialKeys.key_with_modifiers_to_escape_sequence(
handler.modifier_state,
key
)
%{handler | buffer: sequence}
end
@doc """
Processes mouse events.
"""
def process_mouse(%__MODULE__{} = handler, {action, button, x, y}) do
handle_mouse_event(handler.mouse_enabled, handler, action, button, x, y)
end
defp handle_mouse_event(false, handler, _action, _button, _x, _y), do: handler
defp handle_mouse_event(true, handler, action, button, x, y) do
action_code =
case action do
:press -> "M"
:release -> "m"
:drag -> "M"
_ -> ""
end
sequence = "\e[<#{button};#{x + 1};#{y + 1}#{action_code}"
mouse_buttons =
case action do
:press -> MapSet.put(handler.mouse_buttons, button)
:release -> MapSet.delete(handler.mouse_buttons, button)
_ -> handler.mouse_buttons
end
%{
handler
| buffer: sequence,
mouse_position: {x, y},
mouse_buttons: mouse_buttons
}
end
@doc """
Sets mouse enabled state.
"""
def set_mouse_enabled(%__MODULE__{} = handler, enabled) do
%{handler | mouse_enabled: enabled}
end
@doc """
Sets input mode.
"""
def set_mode(%__MODULE__{} = handler, mode) do
%{handler | mode: mode}
end
@doc """
Gets current input mode.
"""
def get_mode(%__MODULE__{} = handler) do
handler.mode
end
@doc """
Adds current buffer to history if not empty.
"""
def add_to_history(%__MODULE__{} = handler) do
add_buffer_to_history(handler.buffer, handler)
end
defp add_buffer_to_history("", handler), do: handler
defp add_buffer_to_history(buffer, handler) do
history = [buffer | handler.input_history]
%{handler | input_history: history, history_index: nil, buffer: ""}
end
@doc """
Gets history entry at specified index.
"""
def get_history_entry(%__MODULE__{} = handler, index) do
history_length = length(handler.input_history)
get_entry_if_valid_index(
index >= 0 and index < history_length,
handler,
index
)
end
defp get_entry_if_valid_index(false, handler, _index), do: handler
defp get_entry_if_valid_index(true, handler, index) do
entry = Enum.at(handler.input_history, index)
%{handler | buffer: entry, history_index: index}
end
@doc """
Moves to next (newer) history entry.
"""
def next_history_entry(%__MODULE__{} = handler) do
handle_next_history(handler.history_index, handler.input_history, handler)
end
defp handle_next_history(nil, _history, handler),
do: {handler, handler.buffer}
defp handle_next_history(_index, [], handler), do: {handler, handler.buffer}
defp handle_next_history(index, history, handler) do
new_index = max(index - 1, 0)
entry = Enum.at(history, new_index)
new_handler = %{handler | buffer: entry, history_index: new_index}
{new_handler, entry}
end
@doc """
Moves to previous (older) history entry.
"""
def previous_history_entry(%__MODULE__{} = handler) do
history_length = length(handler.input_history)
handle_previous_history(handler.history_index, history_length, handler)
end
defp handle_previous_history(nil, history_length, handler)
when history_length > 0 do
entry = hd(handler.input_history)
new_handler = %{handler | buffer: entry, history_index: 0}
{new_handler, entry}
end
defp handle_previous_history(nil, _history_length, handler),
do: {handler, handler.buffer}
defp handle_previous_history(index, history_length, handler)
when index < history_length - 1 do
new_index = index + 1
entry = Enum.at(handler.input_history, new_index)
new_handler = %{handler | buffer: entry, history_index: new_index}
{new_handler, entry}
end
defp handle_previous_history(_index, _history_length, handler),
do: {handler, handler.buffer}
@doc """
Clears the input buffer.
"""
def clear_buffer(%__MODULE__{} = handler) do
%{handler | buffer: ""}
end
@doc """
Checks if buffer is empty.
"""
def buffer_empty?(%__MODULE__{} = handler) do
handler.buffer == ""
end
@doc """
Gets buffer contents.
"""
def get_buffer_contents(%__MODULE__{} = handler) do
handler.buffer
end
@doc """
Handles printable character input for the terminal emulator.
"""
def handle_printable_character(
emulator,
char_codepoint,
_params,
_single_shift
) do
# Processing printable character
# Use the CharacterProcessor to handle the printable character
# This will write the character to the buffer and update cursor position
updated_emulator =
Raxol.Terminal.Input.CharacterProcessor.process_printable_character(
emulator,
char_codepoint
)
{updated_emulator, nil}
end
end