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.ex
defmodule Raxol.Terminal.Input do
@moduledoc """
Handles input processing for the terminal.
"""
defstruct [
:buffer,
:state,
:last_click,
:last_drag,
:last_release,
:completion_callback,
:completion_options,
:completion_index
]
@type completion_callback :: (String.t() -> [String.t()])
@type t :: %__MODULE__{
buffer: list(),
state: atom(),
last_click: {integer(), integer(), atom()} | nil,
last_drag: {integer(), integer(), atom()} | nil,
last_release: {integer(), integer(), atom()} | nil,
completion_callback: completion_callback() | nil,
completion_options: [String.t()],
completion_index: non_neg_integer()
}
@doc """
Creates a new input handler.
"""
def new do
%__MODULE__{
buffer: [],
state: :normal,
last_click: nil,
last_drag: nil,
last_release: nil,
completion_callback: nil,
completion_options: [],
completion_index: 0
}
end
@doc """
Handles a mouse click event.
"""
def handle_click(input, x, y, button) do
%{input | last_click: {x, y, button}}
end
@doc """
Handles a mouse drag event.
"""
def handle_drag(input, x, y, button) do
%{input | last_drag: {x, y, button}}
end
@doc """
Handles a mouse release event.
"""
def handle_release(input, x, y, button) do
%{input | last_release: {x, y, button}}
end
@doc """
Performs tab completion on the input buffer.
Uses the completion_callback to find matches and cycles through them.
"""
def tab_complete(%__MODULE__{completion_callback: nil} = input) do
# No completion callback set, return unchanged
input
end
def tab_complete(%__MODULE__{completion_callback: callback} = input) do
# Convert buffer to string for completion
buffer_string = buffer_to_string(input.buffer)
# Ensure completion_options is a list
completion_options = input.completion_options || []
case completion_options == [] do
true ->
# First tab - get initial completions
options = callback.(buffer_string)
case options do
[] ->
# No matches, return unchanged
input
[single_match] ->
# Single match, complete immediately and clear options
%{
input
| buffer: string_to_buffer(single_match),
completion_options: [],
completion_index: 0
}
multiple_matches ->
# Multiple matches, set up for cycling
first_match = Enum.at(multiple_matches, 0)
%{
input
| buffer: string_to_buffer(first_match),
completion_options: multiple_matches,
completion_index: 0
}
end
false ->
# Subsequent tabs - cycle through existing options
next_index = rem(input.completion_index + 1, length(completion_options))
next_match = Enum.at(completion_options, next_index)
%{
input
| buffer: string_to_buffer(next_match),
completion_index: next_index
}
end
end
@doc """
Clears completion state. Should be called when input changes other than tab completion.
"""
def clear_completion(%__MODULE__{} = input) do
%{input | completion_options: [], completion_index: 0}
end
@doc """
Example completion callback that provides Elixir keywords.
"""
def example_completion_callback(prefix) do
elixir_keywords = [
"def",
"defp",
"defmodule",
"defstruct",
"defprotocol",
"defimpl",
"do",
"end",
"if",
"unless",
"when",
"case",
"cond",
"with",
"try",
"catch",
"rescue",
"after",
"receive",
"for",
"import",
"alias",
"require",
"use",
"quote",
"unquote",
"spawn",
"send",
"fn",
"->",
"true",
"false",
"nil"
]
elixir_keywords
|> Enum.filter(&String.starts_with?(&1, prefix))
|> Enum.sort()
end
# Helper functions
defp buffer_to_string(buffer) when is_list(buffer) do
buffer |> Enum.join("")
end
defp string_to_buffer(string) when is_binary(string) do
String.graphemes(string)
end
end