Class: TuiTui::KeyReader

Inherits:
Object
  • Object
show all
Defined in:
lib/tui_tui/key_reader.rb

Overview

Decodes raw-mode terminal input into literal keys, named keys, and mouse events.

Constant Summary collapse

ESCAPES =
{
  "\e[A" => :up,
  "\e[B" => :down,
  "\e[C" => :right,
  "\e[D" => :left,
  "\eOA" => :up,
  "\eOB" => :down,
  "\eOC" => :right,
  "\eOD" => :left,
  "\e[H" => :home,
  "\e[F" => :end,
  "\eOH" => :home,
  "\eOF" => :end,
  "\e[1~" => :home,
  "\e[4~" => :end,
  "\e[5~" => :pgup,
  "\e[6~" => :pgdn,
  "\e[3~" => :delete,
  "\e[Z" => :backtab
}.freeze
ESCAPE_TAIL_BYTES =
256
MOUSE =

SGR mouse reports can grow with coordinate length; decode the first report if several drag reports arrive in one non-blocking read.

/\A\[<(\d+);(\d+);(\d+)([Mm])/.freeze
MOUSE_MOTION =
0x20
MOUSE_WHEEL =
0x40
MODIFIED =

A modified special key arrives as a CSI with a “1;<mod>” parameter, e.g. Ctrl+Right = “e[1;5C”, Shift+Up = “e[1;2A”, Ctrl+Delete = “e[3;5~”. MOD encodes the held modifiers as (1 + Shift(1) + Alt(2) + Ctrl(4)).

/\A\[(\d*);(\d+)([A-Za-z~])/.freeze
MOD_LETTER =
{"A" => :up, "B" => :down, "C" => :right, "D" => :left, "H" => :home, "F" => :end}.freeze
MOD_TILDE =
{1 => :home, 3 => :delete, 4 => :end, 5 => :pgup, 6 => :pgdn, 7 => :home, 8 => :end}.freeze
MOD_BITS =
{shift: 1, alt: 2, ctrl: 4}.freeze

Instance Method Summary collapse

Instance Method Details

#decode_escape(rest) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/tui_tui/key_reader.rb', line 58

def decode_escape(rest)
  return :escape if rest.nil? || rest.empty?

  mouse = decode_mouse(rest)
  return mouse if mouse

  modified = decode_modified(rest)
  return modified if modified

  ESCAPES["\e" + rest] || :escape
end

#decode_escape_events(rest) ⇒ Object

Decode a whole ESC tail into events, batching consecutive mouse reports (and otherwise yielding a single key/escape).



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/tui_tui/key_reader.rb', line 72

def decode_escape_events(rest)
  return [:escape] if rest.nil? || rest.empty?

  mice = []
  remainder = rest
  loop do
    remainder = remainder.sub(/\A\e/, "")
    match = MOUSE.match(remainder) or break

    mice << mouse_event_from(match)
    remainder = match.post_match
  end

  mice.empty? ? [decode_escape(rest)] : mice
end

#read(io) ⇒ Object

One keypress/event from ‘io` (String, Symbol, or MouseEvent), nil at EOF.



47
# File 'lib/tui_tui/key_reader.rb', line 47

def read(io) = read_all(io)&.first

#read_all(io) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/tui_tui/key_reader.rb', line 49

def read_all(io)
  first = io.getch
  return nil if first.nil?
  return decode_escape_events(read_escape_tail(io)) if first == KeyCode::ESCAPE
  return [assemble_utf8(io, first)] if first.bytesize == 1 && first.getbyte(0) >= 0x80

  [first]
end