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/event/event_handler.ex
defmodule Raxol.Terminal.Event.Handler do
@moduledoc """
Handles terminal events including input events, state changes, and notifications.
This module is responsible for processing and dispatching events to appropriate handlers.
"""
use Raxol.Core.Behaviours.BaseManager
alias Raxol.Terminal.Event
# Client API
@doc """
Creates a new event handler with default values.
"""
def new do
%Event{handlers: %{}, queue: :queue.new()}
end
def register_handler(emulator, event_type, handler) do
case emulator.event do
nil ->
emulator
%Event{} = event_struct ->
# Direct struct mode for testing
new_handlers = Map.put(event_struct.handlers, event_type, handler)
%{emulator | event: %{event_struct | handlers: new_handlers}}
event_pid ->
GenServer.call(event_pid, {:register_handler, event_type, handler})
emulator
end
end
def unregister_handler(emulator, event_type) do
case emulator.event do
nil ->
emulator
%Event{} = event_struct ->
# Direct struct mode for testing
new_handlers = Map.delete(event_struct.handlers, event_type)
%{emulator | event: %{event_struct | handlers: new_handlers}}
event_pid ->
GenServer.call(event_pid, {:unregister_handler, event_type})
emulator
end
end
def queue_event(emulator, event_type, event_data) do
case emulator.event do
nil ->
emulator
%Event{} = event_struct ->
# Direct struct mode for testing
new_queue = :queue.in({event_type, event_data}, event_struct.queue)
%{emulator | event: %{event_struct | queue: new_queue}}
event_pid ->
GenServer.cast(event_pid, {:queue_event, event_type, event_data})
emulator
end
end
def process_events(emulator) do
case emulator.event do
nil ->
emulator
%Event{} = event_struct ->
# Direct struct mode for testing - process all events in queue
process_event_queue(emulator, event_struct)
event_pid ->
GenServer.call(event_pid, :process_events)
emulator
end
end
def get_event_queue(emulator) do
case emulator.event do
nil -> :queue.new()
%Event{} = event_struct -> event_struct.queue
event_pid -> GenServer.call(event_pid, :get_event_queue)
end
end
def clear_event_queue(emulator) do
case emulator.event do
nil ->
emulator
%Event{} = event_struct ->
# Direct struct mode for testing
%{emulator | event: %{event_struct | queue: :queue.new()}}
event_pid ->
GenServer.call(event_pid, :clear_event_queue)
emulator
end
end
# Helper function for processing events in struct mode
defp process_event_queue(emulator, event_struct) do
case :queue.out(event_struct.queue) do
{:empty, _} ->
emulator
{{:value, {event_type, event_data}}, remaining_queue} ->
# Process this event and continue with remaining queue
updated_emulator =
invoke_event_handler(
event_struct.handlers,
event_type,
emulator,
event_data
)
# Update the queue and continue processing
updated_event_struct = %{event_struct | queue: remaining_queue}
updated_emulator = %{updated_emulator | event: updated_event_struct}
process_event_queue(updated_emulator, updated_event_struct)
end
end
def reset_event_handler(emulator) do
case emulator.event do
nil ->
emulator
%Event{} = _event_struct ->
# Direct struct mode for testing - reset to new event
%{emulator | event: new()}
event_pid ->
GenServer.call(event_pid, :reset)
emulator
end
end
def dispatch_event(emulator, event_type, event_data) do
case emulator.event do
nil ->
{:ok, emulator}
%Event{} = event_struct ->
# Direct struct mode for testing
invoke_and_wrap_result(
event_struct.handlers,
event_type,
emulator,
event_data
)
event_pid ->
GenServer.call(
event_pid,
{:dispatch_event, event_type, event_data, emulator}
)
end
end
# BaseManager Callbacks
@impl Raxol.Core.Behaviours.BaseManager
def init_manager(_opts) do
{:ok, %Event{handlers: %{}, queue: :queue.new()}}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(
{:register_handler, event_type, handler},
_from,
state
) do
handlers = Map.put(state.handlers, event_type, handler)
{:reply, :ok, %{state | handlers: handlers}}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call({:unregister_handler, event_type}, _from, state) do
handlers = Map.delete(state.handlers, event_type)
{:reply, :ok, %{state | handlers: handlers}}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(
{:dispatch_event, event_type, event_data, emulator},
_from,
state
) do
case Map.get(state.handlers, event_type) do
nil ->
{:reply, {:ok, emulator}, state}
handler ->
# Pass the actual emulator to the handler
result = handler.(emulator, event_data)
{:reply, result, state}
end
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(:get_event_queue, _from, state) do
{:reply, state.queue, state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(:clear_event_queue, _from, state) do
{:reply, :ok, %{state | queue: :queue.new()}}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(:reset, _from, _state) do
{:reply, :ok, %Event{handlers: %{}, queue: :queue.new()}}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_call(:process_events, _from, state) do
{processed_events, new_state} = process_queued_events(state)
{:reply, processed_events, new_state}
end
@impl Raxol.Core.Behaviours.BaseManager
def handle_manager_cast({:queue_event, event_type, event_data}, state) do
queue = :queue.in({event_type, event_data}, state.queue)
{:noreply, %{state | queue: queue}}
end
# Private functions
defp process_queued_events(state) do
process_queued_events_recursive(state, [])
end
defp process_queued_events_recursive(state, processed_events) do
case :queue.out(state.queue) do
{{:value, {event_type, event_data}}, remaining_queue} ->
case Map.get(state.handlers, event_type) do
nil ->
# No handler for this event, skip it
process_queued_events_recursive(
%{state | queue: remaining_queue},
processed_events
)
handler ->
# Call the handler with a minimal emulator structure
# Note: In queue processing, we don't have the full emulator context
handle_queued_event(
handler,
event_type,
event_data,
state,
remaining_queue,
processed_events
)
end
{:empty, _} ->
{Enum.reverse(processed_events), state}
end
end
defp invoke_event_handler(handlers, event_type, emulator, event_data) do
case Map.get(handlers, event_type) do
nil ->
emulator
handler ->
case handler.(emulator, event_data) do
{:ok, result} -> result
result -> result
end
end
end
defp invoke_and_wrap_result(handlers, event_type, emulator, event_data) do
case Map.get(handlers, event_type) do
nil ->
{:ok, emulator}
handler ->
case handler.(emulator, event_data) do
{:ok, result} -> {:ok, result}
result -> {:ok, result}
end
end
end
defp handle_queued_event(
handler,
event_type,
event_data,
state,
remaining_queue,
processed_events
) do
case handler.(%{event: self()}, event_data) do
{:ok, _result} ->
# Event processed successfully, continue with remaining events
process_queued_events_recursive(
%{state | queue: remaining_queue},
[{event_type, event_data} | processed_events]
)
_ ->
# Event processing failed, skip it
process_queued_events_recursive(
%{state | queue: remaining_queue},
processed_events
)
end
end
end