TermUI.Backend.Selector (TermUI v1.0.0)

View Source

Determines which terminal backend to use at runtime.

The Selector module implements a "try raw mode first" strategy for backend selection. After the platform/version guard, attempting acquisition is the authoritative check for whether native Raw mode is available.

Why Not Use Heuristics?

Environment-based detection (checking $TERM, current I/O device options, etc.) cannot reliably detect all cases where raw mode is unavailable:

  • Nerves devices: The erlinit process may have already started a shell, making raw mode unavailable even though $TERM suggests a capable terminal

  • SSH sessions: Remote SSH connections often have a shell already running in the PTY, preventing raw mode activation

  • Remote IEx: Connecting to a running node via --remsh or distributed Erlang inherits the remote node's terminal state

  • Docker containers: Terminal allocation varies by configuration; a TTY may be allocated but a shell may already be running

  • IDE terminals: Integrated terminals may report capabilities they don't fully support in raw mode

The Selection Strategy

The selector attempts to start raw mode using OTP 28's :shell.start_interactive({:noshell, :raw}):

  1. If raw mode succeeds (returns :ok):

    • The terminal is now in raw mode
    • Return {:raw, state} for the Raw backend
  2. If raw mode fails with {:error, :already_started}:

    • A shell is already running, raw mode unavailable
    • Detect terminal capabilities for graceful degradation
    • Return {:tty, capabilities} for the TTY backend
  3. If the function is undefined (pre-OTP 28):

    • Fall back to TTY mode
    • Return {:tty, capabilities} with detected capabilities

Return Values

The select/0 function returns one of:

  • {:raw, state} - Raw mode is active. The state map contains:

    • :raw_mode_started - true indicating raw mode was activated
  • {:tty, capabilities} - TTY mode should be used. The capabilities map contains:

    • :colors - Color depth (:true_color, :color_256, :color_16, :monochrome)
    • :unicode - Boolean indicating Unicode support
    • :dimensions - {rows, cols} tuple or nil if unknown
    • :terminal - Boolean indicating terminal presence

Explicit Selection

For testing or configuration override, use select/1:

# Select a module explicitly (initialization is performed by Runtime)
{:explicit, TermUI.Backend.TTY, []} = Selector.select(TermUI.Backend.TTY)

{:explicit, TermUI.Backend.Raw, []} = Selector.select(TermUI.Backend.Raw)

# Auto-detect (same as select/0)
result = Selector.select(:auto)

Examples

# Inspect the selected local mode. Runtime owns backend initialization.
case TermUI.Backend.Selector.select() do
  {:raw, _state} -> :raw
  {:tty, _capabilities} -> :tty
end

OTP Version Requirements

  • OTP 28+ on a supported Unix platform: Native Raw selection with :shell.start_interactive/1
  • OTP 27 and earlier: Automatic fallback to TTY mode

Summary

Types

Detected terminal capabilities for TTY mode.

Detected color depth for TTY mode.

State returned when raw mode is successfully activated.

Result of backend selection.

Functions

Selects the appropriate backend by attempting raw mode first.

Selects a backend with explicit mode or module specification.

Types

capabilities()

@type capabilities() :: %{
  colors: color_depth(),
  unicode: boolean(),
  dimensions: {pos_integer(), pos_integer()} | nil,
  terminal: boolean()
}

Detected terminal capabilities for TTY mode.

color_depth()

@type color_depth() :: :true_color | :color_256 | :color_16 | :monochrome

Detected color depth for TTY mode.

raw_state()

@type raw_state() :: %{raw_mode_started: boolean()}

State returned when raw mode is successfully activated.

selection_result()

@type selection_result() ::
  {:raw, raw_state()}
  | {:tty, capabilities()}
  | {:explicit, module(), keyword()}

Result of backend selection.

  • {:raw, state} - Raw mode active, use Raw backend
  • {:tty, capabilities} - TTY mode, use TTY backend with capabilities
  • {:explicit, module, opts} - Explicit backend selection (bypasses detection)

Functions

select()

@spec select() :: {:raw, raw_state()} | {:tty, capabilities()}

Selects the appropriate backend by attempting raw mode first.

Returns {:raw, state} if raw mode succeeds, or {:tty, capabilities} if raw mode is unavailable.

Examples

iex> case TermUI.Backend.Selector.select() do
...>   {:raw, _state} -> :raw_mode
...>   {:tty, _caps} -> :tty_mode
...> end
# Returns :raw_mode or :tty_mode depending on environment

select(module)

@spec select(:auto | module() | {module(), keyword()}) :: selection_result()

Selects a backend with explicit mode or module specification.

Arguments

  • :auto - Same as select/0, auto-detect backend
  • module - Use specific backend module (e.g., TermUI.Backend.TTY)
  • {module, opts} - Use specific backend with options

Examples

# Auto-detect
Selector.select(:auto)

# Force TTY mode
Selector.select(TermUI.Backend.TTY)

# Force with options
Selector.select({TermUI.Backend.TTY, line_mode: :full_redraw})