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 input input_handler.ex
Raw

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