Class: Potty::Input::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/potty/input/decoder.rb

Overview

Turns a raw terminal byte stream into Keys codes. In curses mode the library gets this for free via keypad(true); inline “listen” mode reads raw bytes, so we decode here — and crucially we emit the same integer codes curses would, so every widget’s handle_key works unchanged in either mode.

Printable/control bytes pass straight through as codes. Escape sequences (ESC [ A, ESC O P, ESC [ 3 ~, …) map to Keys::UP / DELETE / etc. A lone ESC is ambiguous — it may begin a sequence — so it’s held until either more bytes complete a sequence, or enough time passes (escape_timeout) that we resolve it to a bare ESC. Same heuristic curses runs internally via ESCDELAY; here it’s ours.

Usage (driven by the inline loop each tick):

keys = decoder.feed(bytes_available, now)   # bytes may be ""
keys.each { |code| view.handle_key(code) }

Constant Summary collapse

SEQUENCES =

Escape sequence (the bytes after ESC) -> Keys code.

{
  '[A' => Keys::UP,    'OA' => Keys::UP,
  '[B' => Keys::DOWN,  'OB' => Keys::DOWN,
  '[C' => Keys::RIGHT, 'OC' => Keys::RIGHT,
  '[D' => Keys::LEFT,  'OD' => Keys::LEFT,
  '[H' => Keys::HOME,  'OH' => Keys::HOME,  '[1~' => Keys::HOME,
  '[F' => Keys::END_,  'OF' => Keys::END_,  '[4~' => Keys::END_,
  '[3~' => Keys::DELETE,
  '[Z' => Keys::SHIFT_TAB
}.freeze
MAX_SEQ =

Longest escape body we might still be completing (e.g. “[3~”).

SEQUENCES.keys.map(&:length).max

Instance Method Summary collapse

Constructor Details

#initialize(escape_timeout: 0.25) ⇒ Decoder

Returns a new instance of Decoder.



39
40
41
42
43
# File 'lib/potty/input/decoder.rb', line 39

def initialize(escape_timeout: 0.25)
  @escape_timeout = escape_timeout
  @buffer = +''
  @esc_at = nil
end

Instance Method Details

#feed(bytes, now) ⇒ Object

Append newly-read bytes (may be empty) and return the key codes that can be resolved now. ‘now` is a monotonic-ish time used only for the bare-ESC timeout; pass Time.now from the loop (and in tests).



48
49
50
51
52
53
54
55
# File 'lib/potty/input/decoder.rb', line 48

def feed(bytes, now)
  @buffer << bytes.to_s
  codes = []
  while (code = take(now))
    codes << code
  end
  codes
end