Packages

Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.

Current section

Files

Jump to
raxol lib raxol terminal input.ex
Raw

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