Module: Rcurses::Input

Included in:
EmojiPicker, Pane
Defined in:
lib/rcurses/input.rb

Instance Method Summary collapse

Instance Method Details

#getchr(t = nil, flush: true) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rcurses/input.rb', line 3

def getchr(t = nil, flush: true)
  begin
    # Reinforce raw mode on every call. External programs (e.g. hyperlist)
    # can leave the terminal in cooked/echo mode; raw! fixes it without
    # the save/restore cycle of getch's raw{} block.
    $stdin.raw!
    # 1) Read a byte (with optional timeout)
    #    Use IO.select for timeout (clean syscall, no background thread).
    if t
      return nil unless IO.select([$stdin], nil, nil, t)
    end
    c = $stdin.getc


    # 2) If it's ESC, read escape sequence byte-by-byte
    #    (greedy read_nonblock can swallow the NEXT keypress)
    seq = c
    if c == "\e"
      if IO.select([$stdin], nil, nil, 0.05)
        begin
          b = $stdin.read_nonblock(1)
          seq << b
          # CSI sequence: \e[ ... (letter or ~)
          # SS3 sequence: \eO (one more byte)
          if b == '[' || b == 'O'
            while IO.select([$stdin], nil, nil, 0.05)
              b = $stdin.read_nonblock(1)
              seq << b
              # CSI terminates on a letter (A-Z, a-z) or ~
              # SS3 terminates after one char
              break if b =~ /[A-Za-z~]/
            end
          end
        rescue IO::WaitReadable, EOFError
        end
      end
    end



    # 3) Single ESC alone
    return "ESC" if seq == "\e"

    # 4) Shift‐TAB
    return "S-TAB" if seq == "\e[Z"

    # 5) Legacy single‐char shift‐arrows (your old working ones)
    case seq
    when "\e[a" then return "S-UP"
    when "\e[b" then return "S-DOWN"
    when "\e[c" then return "S-RIGHT"
    when "\e[d" then return "S-LEFT"
    end

    # 6) CSI style shift‐arrows (e.g. ESC [1;2A )
    if m = seq.match(/\A\e\[\d+;2([ABCD])\z/)
      return { 'A' => "S-UP", 'B' => "S-DOWN", 'C' => "S-RIGHT", 'D' => "S-LEFT" }[m[1]]
    end

    # 6b) CSI style ctrl-arrows (e.g. ESC [1;5A )
    if m = seq.match(/\A\e\[\d+;5([ABCD])\z/)
      return { 'A' => "C-UP", 'B' => "C-DOWN", 'C' => "C-RIGHT", 'D' => "C-LEFT" }[m[1]]
    end

    # 7) Plain arrows and Home/End (xterm style)
    if m = seq.match(/\A\e\[([ABCDHF])\z/)
      return { 'A' => "UP", 'B' => "DOWN", 'C' => "RIGHT", 'D' => "LEFT", 'H' => "HOME", 'F' => "END" }[m[1]]
    end

    # 8) CSI + '~' sequences (Ins, Del, Home, End, PgUp, PgDn, F5-F12)
    if seq.start_with?("\e[") && seq.end_with?("~")
      num = seq[/\d+(?=~)/].to_i
      return case num
            when 1, 7   then "HOME"
            when 2       then "INS"
            when 3       then "DEL"
            when 4, 8    then "END"
            when 5       then "PgUP"
            when 6       then "PgDOWN"
            when 15      then "F5"
            when 17      then "F6"
            when 18      then "F7"
            when 19      then "F8"
            when 20      then "F9"
            when 21      then "F10"
            when 23      then "F11"
            when 24      then "F12"
            else ""
            end
    end

    # 9) SS3 function keys F1-F4, Ctrl+arrows, and Home/End (application mode)
    if seq.start_with?("\eO") && seq.length == 3
      return case seq[2]
            when 'P' then "F1"
            when 'Q' then "F2"
            when 'R' then "F3"
            when 'S' then "F4"
            when 'H' then "HOME"      # SS3 Home (kitty/wezterm application mode)
            when 'F' then "END"       # SS3 End (kitty/wezterm application mode)
            when 'a' then "C-UP"      # Some terminals send ESC O a for Ctrl+Up
            when 'b' then "C-DOWN"    # Some terminals send ESC O b for Ctrl+Down
            when 'c' then "C-RIGHT"   # Some terminals send ESC O c for Ctrl+Right
            when 'd' then "C-LEFT"    # Some terminals send ESC O d for Ctrl+Left
            else ""
            end
    end

    # 10) Single / Ctrl-char mappings
    return case seq
          when "\r", "\n"     then "ENTER"
          when "\t"           then "TAB"
          when "\u007F", "\b" then "BACK"
          when "\u0000"       then "C-SPACE"
          when "\u0001"       then "C-A"
          when "\u0002"       then "C-B"
          when "\u0003"       then "C-C"
          when "\u0004"       then "C-D"
          when "\u0005"       then "C-E"
          when "\u0006"       then "C-F"
          when "\u0007"       then "C-G"
          when "\u0008"       then "C-H"
          when "\u000B"       then "C-K"
          when "\u000C"       then "C-L"
          when "\u000D"       then "C-M"
          when "\u000E"       then "C-N"
          when "\u000F"       then "C-O"
          when "\u0010"       then "C-P"
          when "\u0011"       then "C-Q"
          when "\u0012"       then "C-R"
          when "\u0013"       then "C-S"
          when "\u0014"       then "C-T"
          when "\u0015"       then "C-U"
          when "\u0016"       then "C-V"
          when "\u0018"       then "C-X"
          when "\u0019"       then "C-Y"
          when "\u001A"       then "C-Z"
          when "\u0017"       then "WBACK"
          when /\A[[:print:]]\z/ then seq
          else ""
          end
  ensure
    if flush
      while IO.select([$stdin], nil, nil, 0)
        begin
          $stdin.read_nonblock(4096)
        rescue IO::WaitReadable, EOFError
          break
        end
      end
    end
  end
end