terminal_rb 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/key-codes.rb +5 -5
- data/lib/terminal/ansi/attributes.rb +4 -7
- data/lib/terminal/ansi.rb +31 -42
- data/lib/terminal/detect.rb +21 -21
- data/lib/terminal/input/as_key_event.rb +317 -0
- data/lib/terminal/input.rb +180 -41
- data/lib/terminal/rspec/helper.rb +7 -8
- data/lib/terminal/text/char_width.rb +4 -3
- data/lib/terminal/version.rb +1 -1
- metadata +4 -5
- data/lib/terminal/input/csiu_keys.rb +0 -198
- data/lib/terminal/input/dumb_keys.rb +0 -19
- data/lib/terminal/input/legacy_keys.rb +0 -149
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 357e535e9317609160d54c93a2a106a2a39d86a4c6740e93bbbccc01a2fcd42f
|
4
|
+
data.tar.gz: 746ddada0e50edf61c7220d144d75de46530056138e8a9d1bc1793098fe8a824
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90b2db6aa0cd6d3351ae8b7ec0f8df30a37e7bdad87050def22c47cbf48c94246efe927b15a612adbc713ff8b34971adc973721172da4702a1c7a433446db5e9
|
7
|
+
data.tar.gz: f35bafc5ecc52b59360ecec503e32ed173486fa81b42eec99912ef545d64be888ff01f9d584c3a6f34dd45505d449ece87ae033e61f0bb0e9637d481ddb7f310
|
data/examples/key-codes.rb
CHANGED
@@ -14,9 +14,9 @@ Terminal.hide_cursor
|
|
14
14
|
at_exit { Terminal.show_cursor } # required for some terminals :/
|
15
15
|
|
16
16
|
while true
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
)
|
21
|
-
break puts if
|
17
|
+
event = Terminal.read_key_event or break puts
|
18
|
+
str = "[blue]: [yellow]#{event.raw.inspect}"
|
19
|
+
str << " [bold bright_green]#{event.name}[/]" unless event.simple?
|
20
|
+
Terminal.puts(str)
|
21
|
+
break puts if event.name == 'Esc'
|
22
22
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Terminal
|
4
4
|
module Ansi
|
5
|
-
|
5
|
+
@attr = {
|
6
6
|
'/' => '',
|
7
7
|
'/b' => '22',
|
8
8
|
'/blink' => '25',
|
@@ -109,7 +109,7 @@ module Terminal
|
|
109
109
|
'uu' => '21'
|
110
110
|
}.freeze
|
111
111
|
|
112
|
-
|
112
|
+
@colors = {
|
113
113
|
'/bg' => '49',
|
114
114
|
'/fg' => '39',
|
115
115
|
'/ul' => '59',
|
@@ -200,10 +200,7 @@ module Terminal
|
|
200
200
|
'yellow' => '33'
|
201
201
|
}.freeze
|
202
202
|
|
203
|
-
|
204
|
-
|
205
|
-
COLORS_S = COLORS.transform_keys(&:to_sym).compare_by_identity.freeze
|
206
|
-
|
207
|
-
private_constant :ATTRIBUTES, :COLORS, :ATTRIBUTES_S, :COLORS_S
|
203
|
+
@attr_sym = @attr.transform_keys(&:to_sym).compare_by_identity.freeze
|
204
|
+
@colors_sym = @colors.transform_keys(&:to_sym).compare_by_identity.freeze
|
208
205
|
end
|
209
206
|
end
|
data/lib/terminal/ansi.rb
CHANGED
@@ -12,7 +12,7 @@ module Terminal
|
|
12
12
|
#
|
13
13
|
# @attribute [r] attributes
|
14
14
|
# @return [Array<Symbol>] all attribute names
|
15
|
-
def attributes =
|
15
|
+
def attributes = @attr_sym.keys
|
16
16
|
|
17
17
|
# Supported 3/4-bit color names.
|
18
18
|
#
|
@@ -20,7 +20,7 @@ module Terminal
|
|
20
20
|
#
|
21
21
|
# @attribute [r] colors
|
22
22
|
# @return [Array<Symbol>] all color names
|
23
|
-
def colors =
|
23
|
+
def colors = @colors_sym.keys
|
24
24
|
|
25
25
|
# Supported basic 24-bit (Kitty compatible) color names.
|
26
26
|
#
|
@@ -99,9 +99,9 @@ module Terminal
|
|
99
99
|
.map do |arg|
|
100
100
|
case arg
|
101
101
|
when String
|
102
|
-
|
102
|
+
@attr[arg] || @colors[arg] || _color(arg) || _invalid(arg)
|
103
103
|
when Symbol
|
104
|
-
|
104
|
+
@attr_sym[arg] || @colors_sym[arg] || _color(arg) ||
|
105
105
|
_invalid(arg)
|
106
106
|
when (0..255)
|
107
107
|
"38;5;#{arg}"
|
@@ -121,7 +121,7 @@ module Terminal
|
|
121
121
|
#
|
122
122
|
# @param str [#to_s] object to be tested
|
123
123
|
# @return [true, false] whether if attributes are found
|
124
|
-
def ansi?(str) =
|
124
|
+
def ansi?(str) = @re_test.match?(str.to_s)
|
125
125
|
|
126
126
|
# Decorate given argument with ANSI attributes and colors.
|
127
127
|
#
|
@@ -156,7 +156,7 @@ module Terminal
|
|
156
156
|
# @return [String] string without ANSI attributes
|
157
157
|
def undecorate(str)
|
158
158
|
str = str.to_s
|
159
|
-
str.index("\e") ? str.gsub(
|
159
|
+
str.index("\e") ? str.gsub(@re_test, '') : str.dup
|
160
160
|
end
|
161
161
|
|
162
162
|
# Try to combine given ANSI attributes and colors.
|
@@ -181,7 +181,7 @@ module Terminal
|
|
181
181
|
return if (attributes = attributes.to_s.split(separator)).empty?
|
182
182
|
"\e[#{
|
183
183
|
attributes
|
184
|
-
.map! {
|
184
|
+
.map! { @attr[_1] || @colors[_1] || _color(_1) || return }
|
185
185
|
.join(';')
|
186
186
|
}m"
|
187
187
|
end
|
@@ -196,9 +196,9 @@ module Terminal
|
|
196
196
|
attributes.all? do |arg|
|
197
197
|
case arg
|
198
198
|
when String
|
199
|
-
|
199
|
+
@attr[arg] || @colors[arg] || _color(arg)
|
200
200
|
when Symbol
|
201
|
-
|
201
|
+
@attr_sym[arg] || @colors_sym[arg] || _color(arg)
|
202
202
|
when (0..767)
|
203
203
|
true
|
204
204
|
end
|
@@ -225,7 +225,7 @@ module Terminal
|
|
225
225
|
def bbcode(str)
|
226
226
|
str = str.to_s
|
227
227
|
return str.dup unless str.index('[')
|
228
|
-
str.gsub(
|
228
|
+
str.gsub(@re_bbcode) do |match_str|
|
229
229
|
match = Regexp.last_match(1) or next match_str
|
230
230
|
next "[#{match[1..]}]" if match[0] == '\\'
|
231
231
|
try_convert(match) || match_str
|
@@ -245,11 +245,11 @@ module Terminal
|
|
245
245
|
def unbbcode(str)
|
246
246
|
str = str.to_s
|
247
247
|
return str.dup unless str.index('[')
|
248
|
-
str.gsub(
|
248
|
+
str.gsub(@re_bbcode) do |match_str|
|
249
249
|
match = Regexp.last_match(1) or next match_str
|
250
250
|
next "[#{match[1..]}]" if match[0] == '\\'
|
251
251
|
next match_str if (match = match.split).empty?
|
252
|
-
next if match.all? {
|
252
|
+
next if match.all? { @attr[_1] || @colors[_1] || _color(_1) }
|
253
253
|
match_str
|
254
254
|
end
|
255
255
|
end
|
@@ -270,17 +270,17 @@ module Terminal
|
|
270
270
|
def plain(str)
|
271
271
|
str = str.to_s
|
272
272
|
unless str.index('[')
|
273
|
-
return str.index("\e") ? str.gsub(
|
273
|
+
return str.index("\e") ? str.gsub(@re_test, '') : str.dup
|
274
274
|
end
|
275
275
|
str =
|
276
|
-
str.gsub(
|
276
|
+
str.gsub(@re_bbcode) do |match_str|
|
277
277
|
match = Regexp.last_match(1) or next match_str
|
278
278
|
next "[#{match[1..]}]" if match[0] == '\\'
|
279
279
|
next match_str if (match = match.split).empty?
|
280
|
-
next if match.all? {
|
280
|
+
next if match.all? { @attr[_1] || @colors[_1] || _color(_1) }
|
281
281
|
match_str
|
282
282
|
end
|
283
|
-
str.index("\e") ? str.gsub!(
|
283
|
+
str.index("\e") ? str.gsub!(@re_test, '') : str
|
284
284
|
end
|
285
285
|
|
286
286
|
# Create nice colored text.
|
@@ -292,14 +292,16 @@ module Terminal
|
|
292
292
|
# @return [String] fancy text
|
293
293
|
def rainbow(str, frequency: 0.3, spread: 0.8, seed: 1.1)
|
294
294
|
pos = -1
|
295
|
+
@pi2_third ||= 2.0 * Math::PI / 3.0
|
296
|
+
@pi4_third ||= 4.0 * Math::PI / 3.0
|
295
297
|
str
|
296
298
|
.to_s
|
297
299
|
.chars
|
298
300
|
.map! do |char|
|
299
301
|
i = (seed + ((pos += 1) / spread)) * frequency
|
300
302
|
"\e[38;2;#{(Math.sin(i) * 255).to_i.abs};" \
|
301
|
-
"#{(Math.sin(i +
|
302
|
-
"#{(Math.sin(i +
|
303
|
+
"#{(Math.sin(i + @pi2_third) * 255).to_i.abs};" \
|
304
|
+
"#{(Math.sin(i + @pi4_third) * 255).to_i.abs}m#{char}"
|
303
305
|
end
|
304
306
|
.join << RESET
|
305
307
|
end
|
@@ -533,7 +535,7 @@ module Terminal
|
|
533
535
|
opts << 'v=0'
|
534
536
|
when 1, :bottom
|
535
537
|
opts << 'v=1'
|
536
|
-
when 2, :centered
|
538
|
+
when 2, :centered
|
537
539
|
opts << 'v=2'
|
538
540
|
end
|
539
541
|
case horizontal
|
@@ -541,7 +543,7 @@ module Terminal
|
|
541
543
|
opts << 'h=0'
|
542
544
|
when 1, :right
|
543
545
|
opts << 'h=1'
|
544
|
-
when 2, :centered
|
546
|
+
when 2, :centered
|
545
547
|
opts << 'h=2'
|
546
548
|
end
|
547
549
|
end
|
@@ -568,39 +570,34 @@ module Terminal
|
|
568
570
|
return(
|
569
571
|
case v.size
|
570
572
|
when 1, 2
|
571
|
-
"#{
|
573
|
+
"#{@cbase[b]};5;#{v.hex}"
|
572
574
|
when 3
|
573
|
-
"#{
|
575
|
+
"#{@cbase[b]};2;#{(v[0] * 2).hex};#{
|
574
576
|
(v[1] * 2).hex
|
575
577
|
};#{(v[2] * 2).hex}"
|
576
578
|
when 6
|
577
|
-
"#{
|
579
|
+
"#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
|
578
580
|
end
|
579
581
|
)
|
580
582
|
end
|
581
583
|
b, v = /\A(fg|bg|on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str)&.captures
|
582
584
|
return unless v
|
583
|
-
name = NAMED_COLORS[v] and return "#{
|
585
|
+
name = NAMED_COLORS[v] and return "#{@cbase[b]};#{name}"
|
584
586
|
end
|
585
587
|
end
|
586
588
|
|
587
|
-
|
589
|
+
@cbase =
|
588
590
|
{ 'bg' => '48', 'on' => '48', 'ul' => '58' }.tap { _1.default = '38' }
|
589
591
|
.freeze
|
590
592
|
|
591
|
-
|
593
|
+
@re_test =
|
592
594
|
/
|
593
595
|
(?:\e\[[\d;:\?]*[ABCDEFGHJKSTfminsuhl])
|
594
596
|
|
|
595
597
|
(?:\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
|
596
598
|
/x
|
597
599
|
|
598
|
-
|
599
|
-
|
600
|
-
PI2_THIRD = 2.0 * Math::PI / 3.0
|
601
|
-
PI4_THIRD = 4.0 * Math::PI / 3.0
|
602
|
-
|
603
|
-
private_constant :COLOR_BASE, :TEST, :BBCODE, :PI2_THIRD, :PI4_THIRD
|
600
|
+
@re_bbcode = /(?:\[((?~[\[\]]))\])/
|
604
601
|
|
605
602
|
require_relative 'ansi/attributes'
|
606
603
|
|
@@ -706,15 +703,7 @@ module Terminal
|
|
706
703
|
# "\eM" reverse "\n"
|
707
704
|
# "\e[6n" report Cursor Position as "ESC \[ row ; col R"
|
708
705
|
# "\e[21t report window’s title as ESC ] l title ESC \"
|
709
|
-
# "\e[?
|
710
|
-
#
|
711
|
-
# 1000: VT200_MOUSE
|
712
|
-
# 1002: BTN_EVENT_MOUSE
|
713
|
-
# 1003: ANY_EVENT_MOUSE
|
714
|
-
# 1004: FOCUS_EVENT_MOUSE
|
715
|
-
# 1005: Xterm’s UTF8
|
716
|
-
# 1006: Xterm’s CSI-style
|
717
|
-
# 1015: Urxvt’s CSI-style
|
718
|
-
# "\e[?Xl" reset mouse mode "X"
|
706
|
+
# "\e[?1004h" report focus lost "\e[O" and focus get "\e[I"
|
707
|
+
# (disable by "\e[?1004l")
|
719
708
|
end
|
720
709
|
end
|
data/lib/terminal/detect.rb
CHANGED
@@ -11,9 +11,6 @@ module Terminal
|
|
11
11
|
|
12
12
|
def colors
|
13
13
|
return 16_777_216 if ENV['COLORTERM'] == 'truecolor'
|
14
|
-
if %i[ghostty iterm kitty rio vscode wezterm tabby].include?(app_by_tp)
|
15
|
-
return 16_777_216
|
16
|
-
end
|
17
14
|
term = ENV['TERM'] or return 8
|
18
15
|
return 16_777_216 if /[+-]direct/.match?(term)
|
19
16
|
match = /[-+](\d+)color/.match(term) and return match[1].to_i
|
@@ -32,24 +29,27 @@ module Terminal
|
|
32
29
|
private
|
33
30
|
|
34
31
|
def app_by_tp
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
32
|
+
tp = ENV['TERM_PROGRAM']
|
33
|
+
return if tp.nil? || (tp = tp.strip).empty?
|
34
|
+
case tp
|
35
|
+
when 'Apple_Terminal'
|
36
|
+
:macos
|
37
|
+
when 'CodeEditApp_Terminal'
|
38
|
+
:code_edit
|
39
|
+
when 'docker_desktop'
|
40
|
+
:docker
|
41
|
+
when 'FluentTerminal'
|
42
|
+
:fluent
|
43
|
+
when 'HyperTerm'
|
44
|
+
:hyper
|
45
|
+
when 'iTerm.app'
|
46
|
+
:iterm
|
47
|
+
when 'WarpTerminal'
|
48
|
+
:warp
|
49
|
+
else
|
50
|
+
tp = tp.tr(' \\/?!', '_').downcase
|
51
|
+
tp.empty? ? nil : tp.to_sym
|
52
|
+
end
|
53
53
|
end
|
54
54
|
|
55
55
|
def app_by_term
|
@@ -0,0 +1,317 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Terminal
|
4
|
+
module AsKeyEvent
|
5
|
+
class << self
|
6
|
+
def [](raw)
|
7
|
+
return KeyEvent.new(raw, *@single_key[raw.ord]) if raw.size == 1
|
8
|
+
return KeyEvent.unknown(raw) if raw[0] != "\e"
|
9
|
+
return esc1(raw, raw[1]) if raw.size == 2 # ESC ?
|
10
|
+
case raw[1]
|
11
|
+
when "\e" # ESC ESC ...
|
12
|
+
return esc_esc(raw)
|
13
|
+
when 'O'
|
14
|
+
if raw.size == 3
|
15
|
+
# ESC O ?
|
16
|
+
return KeyEvent.new(raw, *@ss3[raw[2].ord])
|
17
|
+
end
|
18
|
+
when '[' # ESC [ ...
|
19
|
+
if raw.size == 3
|
20
|
+
# ESC [ ?
|
21
|
+
return KeyEvent.new(raw, *@ss3[raw[2].ord])
|
22
|
+
end
|
23
|
+
if raw.size == 6 && raw[2] == 'M'
|
24
|
+
# ESC [ M b c r
|
25
|
+
return mouse_vt200(raw)
|
26
|
+
end
|
27
|
+
return csi1(raw) if raw.start_with?("\e[1;") # ESC [ 1 ; ...
|
28
|
+
case raw[-1]
|
29
|
+
when '~'
|
30
|
+
return legacy(raw)
|
31
|
+
when 'u'
|
32
|
+
return csi_u(raw)
|
33
|
+
when 'M'
|
34
|
+
return raw[2] == '<' ? mouse_sgr(raw) : mouse_urxvt(raw)
|
35
|
+
when 'm'
|
36
|
+
return mouse_sgr(raw) if raw[2] == '<'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
KeyEvent.unknown(raw)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def mouse_vt200(raw)
|
45
|
+
# ESC [ M b c r
|
46
|
+
mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
|
47
|
+
end
|
48
|
+
|
49
|
+
def mouse_sgr(raw)
|
50
|
+
# ESC [ < <code> ; <col> ; <row> M
|
51
|
+
# ESC [ < <code> ; <col> ; <row> m
|
52
|
+
return KeyEvent.unknown(raw) if raw.size < 8
|
53
|
+
bcr = raw[3..-2].split(';', 3).map!(&:to_i)
|
54
|
+
bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
|
55
|
+
end
|
56
|
+
|
57
|
+
def mouse_urxvt(raw)
|
58
|
+
# ESC [ <code> ; <col> ; <row> M
|
59
|
+
return KeyEvent.unknown(raw) if raw.size < 8
|
60
|
+
bcr = raw[2..-2].split(';', 3).map!(&:to_i)
|
61
|
+
bcr.size == 3 ? mouse_event(raw, *bcr) : KeyEvent.unknown(raw)
|
62
|
+
end
|
63
|
+
|
64
|
+
def mouse_event(raw, btn, col, row)
|
65
|
+
return KeyEvent.unknown(raw) if btn < 0 || col < 1 || row < 1
|
66
|
+
kind = btn & 3
|
67
|
+
ka = btn >= 64 ? @mouse_wheel_kind : @mouse_kind
|
68
|
+
btn -= kind
|
69
|
+
modifier = btn.allbits?(4) ? 1 : 0
|
70
|
+
modifier += 2 if btn.allbits?(8)
|
71
|
+
modifier += 4 if btn.allbits?(16)
|
72
|
+
KeyEvent.new(raw, ka[kind], modifier, [col, row])
|
73
|
+
end
|
74
|
+
|
75
|
+
def csi_u(raw)
|
76
|
+
# ESC [ <code> u
|
77
|
+
# ESC [ <code> ; <modifier> u
|
78
|
+
return KeyEvent.unknown(raw) if raw.size < 4
|
79
|
+
idx = raw.index(';')
|
80
|
+
code = raw[2..(idx ? idx - 1 : -2)].to_i
|
81
|
+
KeyEvent.new(
|
82
|
+
raw,
|
83
|
+
@csiu[code] || code.chr(Encoding::UTF_8),
|
84
|
+
idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def legacy(raw)
|
89
|
+
# ESC [ <code> ~
|
90
|
+
# ESC [ <code> ; <modifier> ~
|
91
|
+
return KeyEvent.unknown(raw) if raw.size < 4
|
92
|
+
idx = raw.index(';')
|
93
|
+
KeyEvent.new(
|
94
|
+
raw,
|
95
|
+
@csi[raw[2..(idx ? idx - 1 : -2)].to_i],
|
96
|
+
idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def csi1(raw)
|
101
|
+
# ESC [ 1 ; [~ABCDEFHPQRS]
|
102
|
+
# ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
|
103
|
+
return KeyEvent.unknown(raw) if raw.size < 5
|
104
|
+
key, modifier = @ss3[raw[-1].ord]
|
105
|
+
modifier ||= 0
|
106
|
+
return KeyEvent.new(raw, nil, modifier) unless key
|
107
|
+
modifier |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
|
108
|
+
KeyEvent.new(raw, key, modifier)
|
109
|
+
end
|
110
|
+
|
111
|
+
def esc_esc(raw)
|
112
|
+
return esc1(raw, raw[2]) if raw.size == 3 # ESC ESC ?
|
113
|
+
ret = self[raw[1..]]
|
114
|
+
KeyEvent.new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
|
115
|
+
end
|
116
|
+
|
117
|
+
def esc1(raw, char)
|
118
|
+
key, modifier = @esc1[char.ord]
|
119
|
+
KeyEvent.new(raw, key || char, modifier || 2)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
@mouse_kind = %i[
|
124
|
+
MButton1
|
125
|
+
MButton2
|
126
|
+
MButton3
|
127
|
+
MButtonUp
|
128
|
+
MButton4
|
129
|
+
MButton5
|
130
|
+
].freeze
|
131
|
+
|
132
|
+
@mouse_wheel_kind = %i[MWheelDown MWheelUp MWheelRight MWheelLeft].freeze
|
133
|
+
|
134
|
+
@csiu = {
|
135
|
+
0x02 => :Ins,
|
136
|
+
0x09 => :Tab,
|
137
|
+
0x0d => :Enter,
|
138
|
+
0x1b => :Esc,
|
139
|
+
0x20 => :Space,
|
140
|
+
0x7f => :Back,
|
141
|
+
57_376 => :F13,
|
142
|
+
57_377 => :F14,
|
143
|
+
57_378 => :F15,
|
144
|
+
57_379 => :F16,
|
145
|
+
57_380 => :F17,
|
146
|
+
57_381 => :F18,
|
147
|
+
57_382 => :F19,
|
148
|
+
57_383 => :F20,
|
149
|
+
57_384 => :F21,
|
150
|
+
57_385 => :F22,
|
151
|
+
57_386 => :F23,
|
152
|
+
57_387 => :F24,
|
153
|
+
57_388 => :F25,
|
154
|
+
57_389 => :F26,
|
155
|
+
57_390 => :F27,
|
156
|
+
57_391 => :F28,
|
157
|
+
57_392 => :F29,
|
158
|
+
57_393 => :F30,
|
159
|
+
57_394 => :F31,
|
160
|
+
57_395 => :F32,
|
161
|
+
57_396 => :F33,
|
162
|
+
57_397 => :F34,
|
163
|
+
57_398 => :F35,
|
164
|
+
57_399 => :Kp0,
|
165
|
+
57_400 => :Kp1,
|
166
|
+
57_401 => :Kp2,
|
167
|
+
57_402 => :Kp3,
|
168
|
+
57_403 => :Kp4,
|
169
|
+
57_404 => :Kp5,
|
170
|
+
57_405 => :Kp6,
|
171
|
+
57_406 => :Kp7,
|
172
|
+
57_407 => :Kp8,
|
173
|
+
57_408 => :Kp9,
|
174
|
+
57_409 => :KpDecimal,
|
175
|
+
57_410 => :KpDivide,
|
176
|
+
57_411 => :KpMultiply,
|
177
|
+
57_412 => :KpSubtract,
|
178
|
+
57_413 => :KpAdd,
|
179
|
+
57_414 => :Return, # :KpEnter
|
180
|
+
57_415 => :KpEqual,
|
181
|
+
57_416 => :KpSeparator,
|
182
|
+
57_417 => :KpLeft,
|
183
|
+
57_418 => :KpRight,
|
184
|
+
57_419 => :KpUp,
|
185
|
+
57_420 => :KpDown,
|
186
|
+
57_421 => :KpPageUp,
|
187
|
+
57_422 => :KpPageDown,
|
188
|
+
57_423 => :KpHome,
|
189
|
+
57_424 => :KpEnd,
|
190
|
+
57_425 => :KpInsert,
|
191
|
+
57_426 => :KpDelete,
|
192
|
+
57_428 => :MediaPlay,
|
193
|
+
57_429 => :MediaPause,
|
194
|
+
57_430 => :MediaPlayPause,
|
195
|
+
57_431 => :MediaReverse,
|
196
|
+
57_432 => :MediaStop,
|
197
|
+
57_433 => :MediaFastForward,
|
198
|
+
57_434 => :MediaRewind,
|
199
|
+
57_435 => :MediaTrackNext,
|
200
|
+
57_436 => :MediaTrackPrevious,
|
201
|
+
57_437 => :MediaRecord,
|
202
|
+
57_438 => :LowerVolume,
|
203
|
+
57_439 => :RaiseVolume,
|
204
|
+
57_440 => :MuteVolume,
|
205
|
+
57_441 => :LeftShift,
|
206
|
+
57_442 => :LeftControl,
|
207
|
+
57_443 => :LeftAlt,
|
208
|
+
57_444 => :LeftSuper,
|
209
|
+
57_445 => :LeftHyper,
|
210
|
+
57_446 => :LeftMeta,
|
211
|
+
57_447 => :RightShift,
|
212
|
+
57_448 => :RightControl,
|
213
|
+
57_449 => :RightAlt,
|
214
|
+
57_450 => :RightSuper,
|
215
|
+
57_451 => :RightHyper,
|
216
|
+
57_452 => :RightMeta,
|
217
|
+
57_453 => :IsoLevel3Shift,
|
218
|
+
57_454 => :IsoLevel5Shift
|
219
|
+
}.compare_by_identity.freeze
|
220
|
+
|
221
|
+
@csi = {
|
222
|
+
0x02 => :Ins,
|
223
|
+
0x03 => :Del,
|
224
|
+
0x05 => :PageUp,
|
225
|
+
0x06 => :PageDown,
|
226
|
+
0x07 => :Home,
|
227
|
+
0x08 => :End,
|
228
|
+
0x09 => :Tab,
|
229
|
+
0x0b => :F1,
|
230
|
+
0x0c => :F2,
|
231
|
+
0x0d => :F3,
|
232
|
+
0x0e => :F4,
|
233
|
+
0x0f => :F5,
|
234
|
+
0x11 => :F6,
|
235
|
+
0x12 => :F7,
|
236
|
+
0x13 => :F8,
|
237
|
+
0x14 => :F9,
|
238
|
+
0x15 => :F10,
|
239
|
+
0x17 => :F11,
|
240
|
+
0x18 => :F12,
|
241
|
+
0x19 => :F13,
|
242
|
+
0x1a => :F14,
|
243
|
+
0x1b => :F15,
|
244
|
+
0x1c => :F16,
|
245
|
+
0x1d => :F17,
|
246
|
+
0x1e => :F18,
|
247
|
+
0x1f => :F19,
|
248
|
+
0x20 => :F20,
|
249
|
+
0x21 => :F21,
|
250
|
+
0x22 => :F22,
|
251
|
+
0x23 => :F23,
|
252
|
+
0x24 => :F24
|
253
|
+
}.compare_by_identity.freeze
|
254
|
+
|
255
|
+
@ss3 = {
|
256
|
+
0x41 => :Up, # A
|
257
|
+
0x42 => :Down, # B
|
258
|
+
0x43 => :Right, # C
|
259
|
+
0x44 => :Left, # D
|
260
|
+
0x46 => :End, # F
|
261
|
+
0x48 => :Home, # H
|
262
|
+
0x50 => :F1, # P
|
263
|
+
0x51 => :F2, # Q
|
264
|
+
0x52 => :F3, # R
|
265
|
+
0x53 => :F4, # S
|
266
|
+
0x5a => [:Tab, 1] # Z - not widely used
|
267
|
+
}.compare_by_identity.freeze
|
268
|
+
|
269
|
+
@esc1 = {
|
270
|
+
0x08 => [:Back, 2],
|
271
|
+
0x09 => [:Tab, 2],
|
272
|
+
0x10 => ['?', 6],
|
273
|
+
0x0d => [:Enter, 2],
|
274
|
+
0x1b => [:Esc, 2],
|
275
|
+
0x20 => [:Space, 2],
|
276
|
+
0x7f => [:Back, 2]
|
277
|
+
}.compare_by_identity.freeze
|
278
|
+
|
279
|
+
@single_key = {
|
280
|
+
0x00 => [:Space, 4],
|
281
|
+
0x01 => ['a', 4],
|
282
|
+
0x02 => ['b', 4],
|
283
|
+
0x03 => ['c', 4],
|
284
|
+
0x04 => ['d', 4],
|
285
|
+
0x05 => ['e', 4],
|
286
|
+
0x06 => ['f', 4],
|
287
|
+
0x07 => ['g', 4],
|
288
|
+
0x08 => [:Back, 4],
|
289
|
+
0x09 => :Tab,
|
290
|
+
0x0a => ['j', 4],
|
291
|
+
0x0b => ['k', 4],
|
292
|
+
0x0c => ['l', 4],
|
293
|
+
0x0d => :Enter,
|
294
|
+
0x0e => ['n', 4],
|
295
|
+
0x0f => ['o', 4],
|
296
|
+
0x10 => ['p', 4],
|
297
|
+
0x11 => ['q', 4],
|
298
|
+
0x12 => ['r', 4],
|
299
|
+
0x13 => ['s', 4],
|
300
|
+
0x14 => ['t', 4],
|
301
|
+
0x15 => ['u', 4],
|
302
|
+
0x16 => ['v', 4],
|
303
|
+
0x17 => ['w', 4],
|
304
|
+
0x18 => ['x', 4],
|
305
|
+
0x19 => ['y', 4],
|
306
|
+
0x1a => ['z', 4],
|
307
|
+
0x1b => :Esc,
|
308
|
+
0x1c => ['4', 4],
|
309
|
+
0x1d => ['5', 4],
|
310
|
+
0x1e => ['6', 4],
|
311
|
+
0x1f => ['7', 4],
|
312
|
+
0x20 => :Space,
|
313
|
+
0x7f => :Back
|
314
|
+
}.compare_by_identity.freeze
|
315
|
+
end
|
316
|
+
private_constant :AsKeyEvent
|
317
|
+
end
|
data/lib/terminal/input.rb
CHANGED
@@ -25,82 +25,221 @@ module Terminal
|
|
25
25
|
# The input will be returned as named key codes like "Ctrl+c" by default.
|
26
26
|
# This can be changed by `mode`.
|
27
27
|
#
|
28
|
+
# @deprecated Use arther {read_key_event} for better input handling.
|
29
|
+
#
|
28
30
|
# @param [:named, :raw, :both] mode modifies the result
|
29
31
|
# @return [String] key code ("as is") in `:raw` mode
|
30
32
|
# @return [String] key name in `:named` mode
|
31
33
|
# @return [[String, String]] key code and key name in `:both` mode
|
34
|
+
# @return [nil] in any error case
|
32
35
|
def read_key(mode: :named)
|
33
|
-
|
34
|
-
return
|
35
|
-
|
36
|
-
key, name = read_tty_with(@input_mode == :csi_u ? CSIuKeys : LegacyKeys)
|
37
|
-
return unless key
|
36
|
+
event = read_key_event or return
|
37
|
+
return event.raw if mode == :raw
|
38
|
+
key, name = event
|
38
39
|
mode == :both ? [key, name] : name || key
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
# Read next {KeyEvent} from standard input.
|
43
|
+
#
|
44
|
+
# @return [KeyEvent] next event
|
45
|
+
# @return [nil] on any error
|
46
|
+
def read_key_event
|
47
|
+
case input_mode
|
48
|
+
when :dumb
|
49
|
+
raw = read_dumb or return
|
50
|
+
opts = DUMB_KEYS[raw.ord] if raw.size == 1
|
51
|
+
KeyEvent.new(raw, *opts)
|
52
|
+
when :csi_u, :legacy
|
53
|
+
# raw = with_mouse ? read_tty_with_mouse : read_tty
|
54
|
+
raw = read_tty
|
55
|
+
AsKeyEvent[raw] if raw
|
56
|
+
end
|
49
57
|
end
|
50
58
|
|
51
|
-
|
52
|
-
key, *esc = read_tty
|
53
|
-
return unless key
|
54
|
-
return key, trans.key_name(key) if esc.empty?
|
55
|
-
[key + esc.join, trans.translate(esc)]
|
56
|
-
end
|
59
|
+
private
|
57
60
|
|
58
61
|
def find_input_mode
|
59
62
|
im = ENV['INPUT_MODE']
|
60
|
-
return :dumb if im == 'dumb'
|
63
|
+
return :dumb if im == 'dumb'
|
61
64
|
return :legacy if im == 'legacy'
|
65
|
+
return :dumb unless STDIN.tty?
|
62
66
|
if ansi? && _write("\e[>1u\e[?u\e[c") && csi_u?
|
63
67
|
at_exit { _write("\e[<u") }
|
64
68
|
:csi_u
|
65
69
|
else
|
66
70
|
:legacy
|
67
71
|
end
|
68
|
-
rescue
|
72
|
+
rescue SignalException
|
69
73
|
:legacy
|
70
74
|
rescue IOError, SystemCallError
|
71
75
|
:error
|
72
76
|
end
|
73
77
|
|
74
78
|
def csi_u?
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
inp = +''
|
80
|
+
STDIN.raw { inp << _1.getch until inp.rindex('c') }
|
81
|
+
inp.include?("\e[?1u")
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_dumb
|
85
|
+
STDIN.getc
|
86
|
+
rescue Interrupt
|
87
|
+
"\x05"
|
88
|
+
rescue IOError, SystemCallError
|
89
|
+
@input_mode = :error
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_tty_with_mouse(any_move: false)
|
94
|
+
# highlight: '1001'
|
95
|
+
# drag: '1002'
|
96
|
+
# move: '1003'
|
97
|
+
# ext: '1005'
|
98
|
+
# sgr: '1006'
|
99
|
+
# urxvt: '1015'
|
100
|
+
# pixel: '1016'
|
101
|
+
opts = any_move ? '1000;1003;1006;1015' : '1000;1006;1015'
|
102
|
+
opts = _write("\e[?#{opts}h") ? "\e[?#{opts}l" : nil
|
103
|
+
read_tty
|
104
|
+
ensure
|
105
|
+
_write(opts) if opts
|
82
106
|
end
|
83
107
|
|
84
108
|
def read_tty
|
85
|
-
STDIN.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
String === nc ? key << nc : break
|
90
|
-
end
|
91
|
-
key
|
109
|
+
key = STDIN.getch
|
110
|
+
return key if key != "\e"
|
111
|
+
while (nc = STDIN.read_nonblock(1, exception: false))
|
112
|
+
String === nc ? key << nc : break
|
92
113
|
end
|
93
|
-
|
114
|
+
key
|
115
|
+
rescue SignalException
|
94
116
|
nil
|
95
117
|
rescue IOError, SystemCallError
|
96
118
|
@input_mode = :error
|
97
119
|
nil
|
98
120
|
end
|
121
|
+
|
122
|
+
DUMB_KEYS = {
|
123
|
+
0x05 => ['c', 4],
|
124
|
+
0x08 => :Back,
|
125
|
+
0x09 => :Tab,
|
126
|
+
0x0a => :Enter,
|
127
|
+
0x0d => :Return,
|
128
|
+
0x1b => :Esc
|
129
|
+
}.compare_by_identity.freeze
|
130
|
+
private_constant :DUMB_KEYS
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Key event reported from {read_key_event}.
|
135
|
+
#
|
136
|
+
class KeyEvent
|
137
|
+
class << self
|
138
|
+
# @attribute [w] caching
|
139
|
+
# @return [true, false] whether KeyCodes should be cached
|
140
|
+
def caching = !!@cache
|
141
|
+
|
142
|
+
# @attribute [w] caching
|
143
|
+
def caching=(value)
|
144
|
+
if value
|
145
|
+
@cache ||= {}
|
146
|
+
else
|
147
|
+
@cache = nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def new(raw, key = raw, modifier = 0, extra = nil)
|
152
|
+
@cache ? super.freeze : @cache[raw] || (@cache[raw] = super.freeze)
|
153
|
+
end
|
154
|
+
|
155
|
+
def unknown(raw) = new(raw, nil)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Event string received from standard input.
|
159
|
+
# This can be a simple value like `"a"`or `"\e[24;6~"` (for Shift+Ctrl+F12).
|
160
|
+
#
|
161
|
+
# @return [String] received event string
|
162
|
+
attr_reader :raw
|
163
|
+
|
164
|
+
# Pressed key without any modifiers.
|
165
|
+
# This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
|
166
|
+
# @return [String, Symbol] key without modifiers
|
167
|
+
attr_reader :key
|
168
|
+
|
169
|
+
# Modifier key code. This represents the encoded key modifier like `Shift`
|
170
|
+
# or `Alt`.
|
171
|
+
#
|
172
|
+
# @return [Integer] modifier key code
|
173
|
+
attr_reader :modifier
|
174
|
+
|
175
|
+
# @comment for mouse events? attr_reader :extra
|
176
|
+
|
177
|
+
# Name of the key event.
|
178
|
+
# This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
|
179
|
+
# keys.
|
180
|
+
#
|
181
|
+
# @return [String] key name
|
182
|
+
attr_reader :name
|
183
|
+
|
184
|
+
# @attribute [r] modifier?
|
185
|
+
# @return [true, false] whether a key modifier was pressed
|
186
|
+
def modifier? = @modifier != 0
|
187
|
+
|
188
|
+
# @attribute [r] simple?
|
189
|
+
# @return [true, false] whether a simple char was pressed
|
190
|
+
def simple? = @raw == @name
|
191
|
+
|
192
|
+
# All pressed keys.
|
193
|
+
# This is composed by all {modifier} and the {key}.
|
194
|
+
#
|
195
|
+
# @return [Array<Symbol, String>] all pressed keys
|
196
|
+
def to_a = @ary.dup
|
197
|
+
|
198
|
+
# @!visibility private
|
199
|
+
def to_ary = simple? ? [@raw] : [@raw, @name]
|
200
|
+
|
201
|
+
# @!visibility private
|
202
|
+
def to_s = @name.dup
|
203
|
+
|
204
|
+
# @!visibility private
|
205
|
+
def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
|
206
|
+
|
207
|
+
# @!visibility private
|
208
|
+
def freeze
|
209
|
+
@raw.freeze
|
210
|
+
@key.freeze
|
211
|
+
@extra.freeze
|
212
|
+
@name.freeze
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
def initialize(raw, key, modifier, extra)
|
219
|
+
@raw = raw
|
220
|
+
@key = key
|
221
|
+
@modifier = modifier
|
222
|
+
@extra = extra
|
223
|
+
@ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
|
224
|
+
@ary << key if key
|
225
|
+
@name = @ary.join('+').encode(Encoding::UTF_8)
|
226
|
+
end
|
227
|
+
|
228
|
+
@cache = {}
|
229
|
+
|
230
|
+
MODIFIERS = {
|
231
|
+
1 => :Shift,
|
232
|
+
2 => :Alt,
|
233
|
+
4 => :Ctrl,
|
234
|
+
8 => :Super,
|
235
|
+
16 => :Hyper,
|
236
|
+
32 => :Meta,
|
237
|
+
64 => :Caps,
|
238
|
+
128 => :Num
|
239
|
+
}.freeze
|
240
|
+
private_constant :MODIFIERS
|
99
241
|
end
|
100
242
|
|
101
|
-
|
102
|
-
|
103
|
-
autoload :DumbKeys, "#{dir}/input/dumb_keys.rb"
|
104
|
-
autoload :LegacyKeys, "#{dir}/input/legacy_keys.rb"
|
105
|
-
private_constant :CSIuKeys, :DumbKeys, :LegacyKeys
|
243
|
+
autoload :AsKeyEvent, "#{__dir__}/input/as_key_event.rb"
|
244
|
+
private_constant :AsKeyEvent
|
106
245
|
end
|
@@ -17,14 +17,13 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
|
|
17
17
|
allow(Terminal).to receive(:hide_cursor).with(no_args).and_return(Terminal)
|
18
18
|
allow(Terminal).to receive(:show_cursor).with(no_args).and_return(Terminal)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
nobbc = ansi ? lambda(&:to_s) : ->(s) { Terminal::Ansi.undecorate(s) }
|
20
|
+
if ansi
|
21
|
+
bbc = ->(s) { Terminal::Ansi.bbcode(s) }
|
22
|
+
nobbc = lambda(&:to_s)
|
23
|
+
else
|
24
|
+
bbc = ->(s) { Terminal::Ansi.plain(s) }
|
25
|
+
nobbc = ->(s) { Terminal::Ansi.undecorate(s) }
|
26
|
+
end
|
28
27
|
|
29
28
|
allow(Terminal).to receive(:<<) do |object|
|
30
29
|
stdout.push(bbcode[object]) unless object.nil?
|
@@ -6,9 +6,9 @@ module Terminal
|
|
6
6
|
# Generated file; based on Unicode v16.0.0
|
7
7
|
#
|
8
8
|
module CharWidth
|
9
|
-
def self.[](ord) =
|
9
|
+
def self.[](ord) = @width[@last.bsearch_index { ord <= _1 }]
|
10
10
|
|
11
|
-
|
11
|
+
@last = [
|
12
12
|
0xa0,
|
13
13
|
0xa1,
|
14
14
|
0xa3,
|
@@ -1293,7 +1293,7 @@ module Terminal
|
|
1293
1293
|
0x7fffffff
|
1294
1294
|
].freeze
|
1295
1295
|
|
1296
|
-
|
1296
|
+
@width = [
|
1297
1297
|
1,
|
1298
1298
|
-1,
|
1299
1299
|
1,
|
@@ -2578,6 +2578,7 @@ module Terminal
|
|
2578
2578
|
1
|
2579
2579
|
].freeze
|
2580
2580
|
end
|
2581
|
+
|
2581
2582
|
private_constant :CharWidth
|
2582
2583
|
end
|
2583
2584
|
end
|
data/lib/terminal/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terminal_rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
@@ -34,9 +34,7 @@ files:
|
|
34
34
|
- lib/terminal/ansi/named_colors.rb
|
35
35
|
- lib/terminal/detect.rb
|
36
36
|
- lib/terminal/input.rb
|
37
|
-
- lib/terminal/input/
|
38
|
-
- lib/terminal/input/dumb_keys.rb
|
39
|
-
- lib/terminal/input/legacy_keys.rb
|
37
|
+
- lib/terminal/input/as_key_event.rb
|
40
38
|
- lib/terminal/rspec/helper.rb
|
41
39
|
- lib/terminal/text.rb
|
42
40
|
- lib/terminal/text/char_width.rb
|
@@ -47,9 +45,10 @@ licenses:
|
|
47
45
|
- MIT
|
48
46
|
metadata:
|
49
47
|
rubygems_mfa_required: 'true'
|
48
|
+
yard.run: yard
|
50
49
|
source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
|
51
50
|
bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
|
52
|
-
documentation_uri: https://rubydoc.info/gems/terminal_rb/
|
51
|
+
documentation_uri: https://rubydoc.info/gems/terminal_rb/Terminal
|
53
52
|
rdoc_options: []
|
54
53
|
require_paths:
|
55
54
|
- lib
|
@@ -1,198 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Terminal
|
4
|
-
module CSIuKeys
|
5
|
-
class << self
|
6
|
-
def key_name(key)
|
7
|
-
@key_name[key.ord] if key.size == 1
|
8
|
-
end
|
9
|
-
|
10
|
-
def translate(esc)
|
11
|
-
return if esc[0] != '[' # ESC [ ...
|
12
|
-
return @csi1[esc[1].ord] if esc.size == 2 # ESC [ ?
|
13
|
-
return csi1(esc) if esc[1] == '1' && esc[2] == ';'
|
14
|
-
if esc[-1] == '~'
|
15
|
-
# ESC [ <code> ~
|
16
|
-
# ESC [ <code> ; <modifier> ~
|
17
|
-
return with_modifier(esc) { @csi[_1] }
|
18
|
-
end
|
19
|
-
return if esc[-1] != 'u'
|
20
|
-
# ESC [ <code> u
|
21
|
-
# ESC [ <code> ; <modifier> u
|
22
|
-
with_modifier(esc) { @csiu[_1] || _1.chr(Encoding::UTF_8) }
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def csi1(esc)
|
28
|
-
# ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
|
29
|
-
return if esc.size < 5
|
30
|
-
key = @csi1[esc[-1].ord] or return
|
31
|
-
return key if (mod = esc[3..-2].join.to_i - 1) < 1
|
32
|
-
(@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
|
33
|
-
end
|
34
|
-
|
35
|
-
def with_modifier(esc)
|
36
|
-
return yield(esc[1..-2].join.to_i) if (idx = esc.index(';')).nil?
|
37
|
-
key = yield(esc[1..(idx - 1)].join.to_i) or return
|
38
|
-
return key if (mod = esc[(idx + 1)..-2].join.to_i - 1) < 1
|
39
|
-
(@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
@csiu = {
|
44
|
-
0x02 => 'Ins',
|
45
|
-
0x09 => 'Tab',
|
46
|
-
0x0d => 'Enter',
|
47
|
-
0x1b => 'Esc',
|
48
|
-
0x20 => 'Space',
|
49
|
-
0x7f => 'Back',
|
50
|
-
57_376 => 'F13',
|
51
|
-
57_377 => 'F14',
|
52
|
-
57_378 => 'F15',
|
53
|
-
57_379 => 'F16',
|
54
|
-
57_380 => 'F17',
|
55
|
-
57_381 => 'F18',
|
56
|
-
57_382 => 'F19',
|
57
|
-
57_383 => 'F20',
|
58
|
-
57_384 => 'F21',
|
59
|
-
57_385 => 'F22',
|
60
|
-
57_386 => 'F23',
|
61
|
-
57_387 => 'F24',
|
62
|
-
57_388 => 'F25',
|
63
|
-
57_389 => 'F26',
|
64
|
-
57_390 => 'F27',
|
65
|
-
57_391 => 'F28',
|
66
|
-
57_392 => 'F29',
|
67
|
-
57_393 => 'F30',
|
68
|
-
57_394 => 'F31',
|
69
|
-
57_395 => 'F32',
|
70
|
-
57_396 => 'F33',
|
71
|
-
57_397 => 'F34',
|
72
|
-
57_398 => 'F35',
|
73
|
-
57_399 => 'Kp0',
|
74
|
-
57_400 => 'Kp1',
|
75
|
-
57_401 => 'Kp2',
|
76
|
-
57_402 => 'Kp3',
|
77
|
-
57_403 => 'Kp4',
|
78
|
-
57_404 => 'Kp5',
|
79
|
-
57_405 => 'Kp6',
|
80
|
-
57_406 => 'Kp7',
|
81
|
-
57_407 => 'Kp8',
|
82
|
-
57_408 => 'Kp9',
|
83
|
-
57_409 => 'KpDecimal',
|
84
|
-
57_410 => 'KpDivide',
|
85
|
-
57_411 => 'KpMultiply',
|
86
|
-
57_412 => 'KpSubtract',
|
87
|
-
57_413 => 'KpAdd',
|
88
|
-
57_414 => 'Return', # 'KpEnter'
|
89
|
-
57_415 => 'KpEqual',
|
90
|
-
57_416 => 'KpSeparator',
|
91
|
-
57_417 => 'KpLeft',
|
92
|
-
57_418 => 'KpRight',
|
93
|
-
57_419 => 'KpUp',
|
94
|
-
57_420 => 'KpDown',
|
95
|
-
57_421 => 'KpPage_up',
|
96
|
-
57_422 => 'KpPage_down',
|
97
|
-
57_423 => 'KpHome',
|
98
|
-
57_424 => 'KpEnd',
|
99
|
-
57_425 => 'KpInsert',
|
100
|
-
57_426 => 'KpDelete',
|
101
|
-
57_428 => 'MediaPlay',
|
102
|
-
57_429 => 'MediaPause',
|
103
|
-
57_430 => 'MediaPlay Pause',
|
104
|
-
57_431 => 'MediaReverse',
|
105
|
-
57_432 => 'MediaStop',
|
106
|
-
57_433 => 'MediaFast Forward',
|
107
|
-
57_434 => 'MediaRewind',
|
108
|
-
57_435 => 'MediaTrack Next',
|
109
|
-
57_436 => 'MediaTrack Previous',
|
110
|
-
57_437 => 'MediaRecord',
|
111
|
-
57_438 => 'LowerVolume',
|
112
|
-
57_439 => 'RaiseVolume',
|
113
|
-
57_440 => 'MuteVolume',
|
114
|
-
57_441 => 'LeftShift',
|
115
|
-
57_442 => 'LeftControl',
|
116
|
-
57_443 => 'LeftAlt',
|
117
|
-
57_444 => 'LeftSuper',
|
118
|
-
57_445 => 'LeftHyper',
|
119
|
-
57_446 => 'LeftMeta',
|
120
|
-
57_447 => 'RightShift',
|
121
|
-
57_448 => 'RightControl',
|
122
|
-
57_449 => 'RightAlt',
|
123
|
-
57_450 => 'RightSuper',
|
124
|
-
57_451 => 'RightHyper',
|
125
|
-
57_452 => 'RightMeta',
|
126
|
-
57_453 => 'IsoLevel3Shift',
|
127
|
-
57_454 => 'IsoLevel5Shift'
|
128
|
-
}.compare_by_identity.freeze
|
129
|
-
|
130
|
-
@csi1 = {
|
131
|
-
0x41 => 'Up', # A
|
132
|
-
0x42 => 'Down', # B
|
133
|
-
0x43 => 'Right', # C
|
134
|
-
0x44 => 'Left', # D
|
135
|
-
0x46 => 'End', # F
|
136
|
-
0x48 => 'Home', # H
|
137
|
-
0x50 => 'F1', # P
|
138
|
-
0x51 => 'F2', # Q
|
139
|
-
0x52 => 'F3', # R
|
140
|
-
0x53 => 'F4' # S
|
141
|
-
}.compare_by_identity.freeze
|
142
|
-
|
143
|
-
@csi = {
|
144
|
-
0x02 => 'Ins',
|
145
|
-
0x03 => 'Del',
|
146
|
-
0x05 => 'PageUp',
|
147
|
-
0x06 => 'PageDown',
|
148
|
-
0x07 => 'Home',
|
149
|
-
0x08 => 'End',
|
150
|
-
0x09 => 'Tab',
|
151
|
-
0x0b => 'F1',
|
152
|
-
0x0c => 'F2',
|
153
|
-
0x0d => 'F3',
|
154
|
-
0x0e => 'F4',
|
155
|
-
0x0f => 'F5',
|
156
|
-
0x11 => 'F6',
|
157
|
-
0x12 => 'F7',
|
158
|
-
0x13 => 'F8',
|
159
|
-
0x14 => 'F9',
|
160
|
-
0x15 => 'F10',
|
161
|
-
0x17 => 'F11',
|
162
|
-
0x18 => 'F12',
|
163
|
-
0x19 => 'F13',
|
164
|
-
0x1a => 'F14',
|
165
|
-
0x1b => 'F15',
|
166
|
-
0x1c => 'F16',
|
167
|
-
0x1d => 'F17',
|
168
|
-
0x1e => 'F18',
|
169
|
-
0x1f => 'F19',
|
170
|
-
0x20 => 'F20',
|
171
|
-
0x21 => 'F21',
|
172
|
-
0x22 => 'F22',
|
173
|
-
0x23 => 'F23',
|
174
|
-
0x24 => 'F24'
|
175
|
-
}.compare_by_identity.freeze
|
176
|
-
|
177
|
-
@key_name = {
|
178
|
-
0x04 => 'Del',
|
179
|
-
0x09 => 'Tab',
|
180
|
-
0x0d => 'Enter',
|
181
|
-
0x1b => 'Esc',
|
182
|
-
0x20 => 'Space',
|
183
|
-
0x7f => 'Back'
|
184
|
-
}.compare_by_identity.freeze
|
185
|
-
|
186
|
-
@mods = {
|
187
|
-
1 => 'Shift',
|
188
|
-
2 => 'Alt',
|
189
|
-
4 => 'Ctrl',
|
190
|
-
8 => 'Super',
|
191
|
-
16 => 'Hyper',
|
192
|
-
32 => 'Meta',
|
193
|
-
64 => 'Caps',
|
194
|
-
128 => 'Num'
|
195
|
-
}.freeze
|
196
|
-
end
|
197
|
-
private_constant :CSIuKeys
|
198
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Terminal
|
4
|
-
module DumbKeys
|
5
|
-
def self.key_name(key)
|
6
|
-
@key_name[key.ord] if key.size == 1
|
7
|
-
end
|
8
|
-
|
9
|
-
@key_name = {
|
10
|
-
0x05 => 'Ctrl+c',
|
11
|
-
0x08 => 'Back',
|
12
|
-
0x09 => 'Tab',
|
13
|
-
0x0a => 'Enter',
|
14
|
-
0x0d => 'Return',
|
15
|
-
0x1b => 'Esc'
|
16
|
-
}.compare_by_identity.freeze
|
17
|
-
end
|
18
|
-
private_constant :DumbKeys
|
19
|
-
end
|
@@ -1,149 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Terminal
|
4
|
-
module LegacyKeys
|
5
|
-
class << self
|
6
|
-
def key_name(key)
|
7
|
-
@key_name[key.ord] if key.size == 1
|
8
|
-
end
|
9
|
-
|
10
|
-
def translate(esc)
|
11
|
-
return "Alt+#{@esc1[esc[0].ord] || esc[0]}" if esc.size == 1 # ESC ?
|
12
|
-
if esc.size == 2 && (esc[0] == 'O' || esc[0] == '[')
|
13
|
-
return @ss3[esc[1].ord] # ESC O ? || ESC [ ?
|
14
|
-
end
|
15
|
-
if esc[0] == "\e" # ESC ESC ...
|
16
|
-
return "Alt+#{@esc1[esc[1].ord] || esc[1]}" if esc.size == 1
|
17
|
-
esc.shift
|
18
|
-
return (name = translate(esc)) ? "Alt+#{name}" : nil
|
19
|
-
end
|
20
|
-
return if esc[0] != '[' # ESC [ ...
|
21
|
-
return csi1(esc) if esc[1] == '1' && esc[2] == ';'
|
22
|
-
csi(esc) if esc[-1] == '~'
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def csi(esc)
|
28
|
-
# ESC [ <code> ~
|
29
|
-
# ESC [ <code> ; <modifier> ~
|
30
|
-
return @csi[esc[1..-2].join.to_i] if (idx = esc.index(';')).nil? # no mod
|
31
|
-
key = @csi[esc[1..(idx - 1)].join.to_i] or return
|
32
|
-
return key if (mod = esc[(idx + 1)..-2].join.to_i - 1) < 1 # no mod
|
33
|
-
(@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
|
34
|
-
end
|
35
|
-
|
36
|
-
def csi1(esc)
|
37
|
-
# ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
|
38
|
-
return if esc.size < 5
|
39
|
-
key = @ss3[esc[-1].ord] or return
|
40
|
-
return key if (mod = esc[3..-2].join.to_i - 1) < 1 # no mod
|
41
|
-
(@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
@csi = {
|
46
|
-
0x02 => 'Ins',
|
47
|
-
0x03 => 'Del',
|
48
|
-
0x05 => 'PageUp',
|
49
|
-
0x06 => 'PageDown',
|
50
|
-
0x07 => 'Home',
|
51
|
-
0x08 => 'End',
|
52
|
-
0x09 => 'Tab',
|
53
|
-
0x0b => 'F1',
|
54
|
-
0x0c => 'F2',
|
55
|
-
0x0d => 'F3',
|
56
|
-
0x0e => 'F4',
|
57
|
-
0x0f => 'F5',
|
58
|
-
0x11 => 'F6',
|
59
|
-
0x12 => 'F7',
|
60
|
-
0x13 => 'F8',
|
61
|
-
0x14 => 'F9',
|
62
|
-
0x15 => 'F10',
|
63
|
-
0x17 => 'F11',
|
64
|
-
0x18 => 'F12',
|
65
|
-
0x19 => 'F13',
|
66
|
-
0x1a => 'F14',
|
67
|
-
0x1b => 'Enter',
|
68
|
-
0x1c => 'F15',
|
69
|
-
0x1d => 'F16',
|
70
|
-
0x1f => 'F17',
|
71
|
-
0x20 => 'F18',
|
72
|
-
0x21 => 'F19',
|
73
|
-
0x22 => 'F20'
|
74
|
-
}.compare_by_identity.freeze
|
75
|
-
|
76
|
-
@ss3 = {
|
77
|
-
0x41 => 'Up', # A
|
78
|
-
0x42 => 'Down', # B
|
79
|
-
0x43 => 'Right', # C
|
80
|
-
0x44 => 'Left', # D
|
81
|
-
0x46 => 'End', # F
|
82
|
-
0x48 => 'Home', # H
|
83
|
-
0x50 => 'F1', # P
|
84
|
-
0x51 => 'F2', # Q
|
85
|
-
0x52 => 'F3', # R
|
86
|
-
0x53 => 'F4', # S
|
87
|
-
0x5a => 'Shift+Tab' # Z - not widely used
|
88
|
-
}.compare_by_identity.freeze
|
89
|
-
|
90
|
-
@esc1 = {
|
91
|
-
0x08 => 'Back',
|
92
|
-
0x09 => 'Tab',
|
93
|
-
0x10 => 'Ctrl+?',
|
94
|
-
0x0d => 'Enter',
|
95
|
-
0x1b => 'Esc',
|
96
|
-
0x20 => 'Space',
|
97
|
-
0x7f => 'Back'
|
98
|
-
}.compare_by_identity.freeze
|
99
|
-
|
100
|
-
@key_name = {
|
101
|
-
0x00 => 'Ctrl+Space',
|
102
|
-
0x01 => 'Ctrl+a',
|
103
|
-
0x02 => 'Ctrl+b',
|
104
|
-
0x03 => 'Ctrl+c',
|
105
|
-
0x04 => 'Ctrl+d',
|
106
|
-
0x05 => 'Ctrl+e',
|
107
|
-
0x06 => 'Ctrl+f',
|
108
|
-
0x07 => 'Ctrl+g',
|
109
|
-
0x08 => 'Ctrl+Back',
|
110
|
-
0x09 => 'Tab',
|
111
|
-
0x0a => 'Ctrl+j',
|
112
|
-
0x0b => 'Ctrl+k',
|
113
|
-
0x0c => 'Ctrl+l',
|
114
|
-
0x0d => 'Enter',
|
115
|
-
0x0e => 'Ctrl+n',
|
116
|
-
0x0f => 'Ctrl+o',
|
117
|
-
0x10 => 'Ctrl+p',
|
118
|
-
0x11 => 'Ctrl+q',
|
119
|
-
0x12 => 'Ctrl+r',
|
120
|
-
0x13 => 'Ctrl+s',
|
121
|
-
0x14 => 'Ctrl+t',
|
122
|
-
0x15 => 'Ctrl+u',
|
123
|
-
0x16 => 'Ctrl+v',
|
124
|
-
0x17 => 'Ctrl+w',
|
125
|
-
0x18 => 'Ctrl+x',
|
126
|
-
0x19 => 'Ctrl+y',
|
127
|
-
0x1a => 'Ctrl+z',
|
128
|
-
0x1b => 'Esc',
|
129
|
-
0x1c => 'Ctrl+4',
|
130
|
-
0x1d => 'Ctrl+5',
|
131
|
-
0x1e => 'Ctrl+6',
|
132
|
-
0x1f => 'Ctrl+7',
|
133
|
-
0x20 => 'Space',
|
134
|
-
0x7f => 'Back'
|
135
|
-
}.compare_by_identity.freeze
|
136
|
-
|
137
|
-
@mods = {
|
138
|
-
1 => 'Shift',
|
139
|
-
2 => 'Alt',
|
140
|
-
4 => 'Ctrl',
|
141
|
-
8 => 'Super',
|
142
|
-
16 => 'Hyper',
|
143
|
-
32 => 'Meta',
|
144
|
-
64 => 'Caps',
|
145
|
-
128 => 'Num'
|
146
|
-
}.freeze
|
147
|
-
end
|
148
|
-
private_constant :LegacyKeys
|
149
|
-
end
|