arizona_terminal_io (arizona v0.4.0)

View Source

Low-level terminal byte helpers -- the arizona complement to OTP's io_ansi, which only emits output sequences. This module starts on the input side and has room to grow (CRLF, mouse, ...).

keys/1 translates a raw terminal read into a list of idiomatic keys, so a driver (see arizona_terminal_driver) can pattern-match on them rather than on raw bytes:

  • a printable character is its code -- a -> $a, + -> $+, space -> $\\s;
  • named and escape keys are atoms -- up, down, left, right, enter, tab, backspace, escape, home, 'end', page_up, page_down, insert, delete;
  • modified keys are tuples -- {ctrl, $c} (Ctrl-C), {alt, $a} (Alt-a).

A read can carry several keypresses (fast typing or a paste), so it returns a list.

Covers ASCII + the keys above. Not yet handled (skipped or split per byte): UTF-8 multibyte input and the function-key set (f1..f12); unrecognised escape sequences are dropped.

Summary

Functions

Decode a raw terminal read into idiomatic keys.

Split a read into its leading decodable bytes and a trailing incomplete escape sequence. A read may end mid-sequence -- a transport delivers bytes in chunks (the local TTY reads fixed-size chunks; a network transport fragments on TCP boundaries), so an escape sequence like \\e[A (an arrow key) can be cut after \\e[. Decoded in isolation that tail is lost and the next read's A mis-decodes into a spurious $A.

Types

key()

-nominal key() :: char() | atom() | {ctrl, char()} | {alt, char()}.

Functions

keys/1

-spec keys(binary()) -> [key()].

Decode a raw terminal read into idiomatic keys.

take_incomplete(Bytes)

-spec take_incomplete(binary()) -> {binary(), binary()}.

Split a read into its leading decodable bytes and a trailing incomplete escape sequence. A read may end mid-sequence -- a transport delivers bytes in chunks (the local TTY reads fixed-size chunks; a network transport fragments on TCP boundaries), so an escape sequence like \\e[A (an arrow key) can be cut after \\e[. Decoded in isolation that tail is lost and the next read's A mis-decodes into a spurious $A.

Returns {Complete, Incomplete}: Complete decodes cleanly via keys/1, and a driver carries Incomplete in its state to prepend to the next read. The incomplete tail is the trailing bytes of an unfinished CSI (\\e[ + parameter/intermediate bytes, awaiting a final byte), an unfinished SS3 introducer (\\eO), or a lone trailing \\e (which could begin any of those). A trailing lone \\e therefore holds until the next read -- the classic terminal ESC-vs-sequence ambiguity, resolved here by buffering rather than a timer.

The held tail is capped (real key sequences are tiny): a "CSI" still unfinished past ~64 bytes is a hostile or garbage stream, not a split key, and holding it would grow the pending buffer -- rescanned per read -- without bound. Past the cap the tail is returned in Complete instead, where keys/1 consumes and drops the unterminated sequence.