TermUI.Input.Raw (TermUI v1.0.0)
View SourceRaw mode input handler implementing the TermUI.Input behaviour.
This module provides synchronous input polling with timeout support for
applications running with the Raw backend. It reads single characters
from stdin and parses escape sequences into TermUI.Event structs.
TermUI.Runtime selects this handler for Raw mode only when
use_input_handler: true. The default Raw path uses the asynchronous
TermUI.Terminal.InputReader instead.
Features
- Non-blocking input: Supports timeout-based polling (including 0ms for non-blocking checks)
- Escape sequence parsing: Handles arrow keys, function keys, mouse events, and other terminal escape sequences
- Buffer management: Maintains partial escape sequences between poll calls
- Security: Buffer and queue size limits prevent memory exhaustion
Usage
# Create initial state
state = TermUI.Input.Raw.new()
# Poll for input with 100ms timeout
case TermUI.Input.Raw.poll(state, 100) do
{{:ok, event}, new_state} -> handle_event(event, new_state)
{:timeout, new_state} -> handle_idle(new_state)
{:eof, new_state} -> handle_shutdown(new_state)
endHow It Works
The module spawns a Task to read from stdin using :io.get_chars/2 (Erlang's
IO module directly). This is critical for compatibility with raw mode activated
via :shell.start_interactive({:noshell, :raw}), which redirects standard
input. Elixir's IO.getn/2 wrapper cannot access the redirected input, but
:io.get_chars/2 works correctly.
Since :io.get_chars/2 blocks until input is available, using a Task allows
us to implement timeout semantics via Task.yield/2.
When an escape sequence spans multiple reads (e.g., arrow keys send multiple bytes), the partial sequence is buffered and completed on subsequent polls.
Escape Sequence Timeout
When a partial escape sequence is detected (e.g., lone ESC), the handler waits
up to 50ms for completion. This matches standard terminal emulator behavior
and distinguishes ESC key presses from escape sequences. The 50ms timeout is
the same value used by TermUI.Terminal.InputReader.
Comparison with InputReader
Unlike TermUI.Terminal.InputReader which is a GenServer that asynchronously
sends events to a target process, this module provides synchronous polling
suitable for use with the TermUI.Input behaviour interface. This module
uses direct :io.get_chars/2 calls wrapped in Tasks for timeout support, rather
than delegating to InputReader, because InputReader's async message-based
design is incompatible with the synchronous polling contract.
Both modules use the same underlying approach (:io.get_chars/2) for reading
from stdin, ensuring compatibility with raw mode's redirected input.
Summary
Functions
Returns the input mode for this handler.
Creates a new Raw input handler state.
Polls for input with the specified timeout.
Stops the Raw input handler.
Types
@type t() :: %TermUI.Input.Raw{buffer: binary(), event_queue: [TermUI.Event.t()]}
State for the Raw input handler.
:buffer- Binary buffer for partial escape sequences:event_queue- Queue of parsed events waiting to be returned
Functions
@spec mode(t()) :: :raw
Returns the input mode for this handler.
Always returns :raw for the Raw input handler.
Examples
mode = Raw.mode(state)
# => :raw
@spec new() :: t()
Creates a new Raw input handler state.
Examples
state = TermUI.Input.Raw.new()
@spec poll(t(), non_neg_integer()) :: TermUI.Input.poll_result()
Polls for input with the specified timeout.
Reads input from stdin and parses it into events. The timeout specifies the maximum time to wait for input in milliseconds. Use 0 for non-blocking polls.
Parameters
state- Current handler statetimeout- Maximum wait time in milliseconds
Returns
{{:ok, event}, new_state}- An event was received{:timeout, new_state}- No input within timeout{:eof, new_state}- End of input stream
Examples
# Non-blocking check
{result, state} = Raw.poll(state, 0)
# Wait up to 100ms
{result, state} = Raw.poll(state, 100)
@spec stop(t()) :: :ok
Stops the Raw input handler.
For the Raw handler, this is a no-op since the InputReader GenServer
is managed separately by the Runtime. This function exists for
compatibility with the TermUI.Input behaviour.
Examples
:ok = Raw.stop(state)