terminal_rb 0.10.0 → 0.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b6f4c3e8cfa6ca602f831ef011a146ff0f70d77d9a0da4a7a37a9768e52da39
4
- data.tar.gz: 73f1ce0355077b7803c7c03924aaa764e8dac2f05e2534a653f37ba4b7d32b27
3
+ metadata.gz: 82d95e0e2e8423c8bbb464019e8282bb589c5bc8fb02298ec529ff58e9ee67bb
4
+ data.tar.gz: bbb98fffca9d97e21a5440644a1ddc0b91dc60e945af287da60dbab07b6ae63b
5
5
  SHA512:
6
- metadata.gz: 2e65c5d198050d41671d57fb5a60c67efd0d4f22c4da71cb4253e8260481fa5c0bc32713be85bf4d1c14a3e5eb7ad886f088ff78418059197b89bf685615a152
7
- data.tar.gz: 9376328d7d488c039ce14411061ee41f4f46efa9ee244c33481f03a855bd6861e871248f3e5faee2b07826b09646e3789a09fd3e2398784c801d527d784114bf
6
+ metadata.gz: 44d16eba18775b6bbd208d19288bd44265a30301607136cafb959e985e59248820829383dd714c62f235880d30c46f4ce0a86a46d46dd8b6f05e042f73068c20
7
+ data.tar.gz: 80b0d17ece194d7fa8b36080314c21683bb1d7739344529bbf47c7da8afbc27c5c910a860393bf348edaef2a27e590d2b2505ab7d7d8934ae349eed152653144
data/lib/terminal/ansi.rb CHANGED
@@ -462,14 +462,7 @@ module Terminal
462
462
  }K"
463
463
  end
464
464
 
465
- # Set window title.
466
- # This is not widely supported.
467
- #
468
- # @param [#to_s] title text
469
- # @return (see cursor_up)
470
- def title(title) = "\e]2;#{title}\a"
471
-
472
- # Set tab title.
465
+ # Set (tab) title.
473
466
  # This is not widely supported; works for
474
467
  # Hyper,
475
468
  # iTerm2,
@@ -478,9 +471,9 @@ module Terminal
478
471
  # Tabby,
479
472
  # WezTerm.
480
473
  #
481
- # @param (see title)
474
+ # @param [#to_s] title text
482
475
  # @return (see cursor_up)
483
- def tab_title(title) = "\e]0;#{title}\a"
476
+ def title(title) = "\e]0;#{title}\a"
484
477
 
485
478
  # Create a hyperlink.
486
479
  # This is not widely supported; works for
@@ -677,15 +670,26 @@ module Terminal
677
670
  LINE_ERASE_PREV = "#{cursor_prev_line(nil)}#{LINE_ERASE}".freeze
678
671
 
679
672
  # @comment seems not widely supported:
680
- # doubled!? def cursor_column(column = 1) = "\e[#{column}`"
681
- # doubled!? def cursor_row(row = 1) = "\e[#{row}d"
673
+ # doubled: def cursor_column(column = 1) = "\e[#{column}`"
674
+ # doubled: def cursor_row(row = 1) = "\e[#{row}d"
675
+ # doubled: def cursor_pos(row, col) = "\e[#{row};#{col}f"
676
+ #
682
677
  # def cursor_column_rel(columns = 1) = "\e[#{columns}a"
683
678
  # def cursor_row_rel(rows = 1) = "\e[#{rows}e"
684
679
  # def cursor_tab(count = 1) = "\e[#{column}I"
685
680
  # def cursor_reverse_tab(count = 1) = "\e[#{count}Z"
681
+ #
682
+ # def lines_insert(count = 1) = "\e[#{count}L"
683
+ # def lines_delete(count = 1) = "\e[#{count}M"
684
+ #
685
+ # def chars_insert_spaces(count = 1) = "\e[#{count}@"
686
686
  # def chars_delete(count = 1) = "\e[#{count}P"
687
687
  # def chars_erase(count = 1) = "\e[#{count}X"
688
+ #
689
+ # def chars_repeat_last(count = 1) = "\e[#{count}b"
690
+ #
688
691
  # def notify(title) = "\e]9;#{title}\a"
692
+ #
689
693
  # def set_scroll_region(top = nil, bottom = nil) = "\e[#{top};#{bottom}r"
690
694
 
691
695
  # @comment TODO:
@@ -695,5 +699,22 @@ module Terminal
695
699
  # https://sw.kovidgoyal.net/kitty/color-stack
696
700
  # https://sw.kovidgoyal.net/kitty/deccara
697
701
  # https://sw.kovidgoyal.net/kitty/clipboard
702
+
703
+ # @comment other:
704
+ # "\eE" same as "\r\n"
705
+ # "\eD" same as "\n" but preserves X coord
706
+ # "\eM" reverse "\n"
707
+ # "\e[6n" report Cursor Position as "ESC \[ row ; col R"
708
+ # "\e[21t report window’s title as ESC ] l title ESC \"
709
+ # "\e[?Xl" set mouse mode "X":
710
+ # 9: X10_MOUSE
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"
698
719
  end
699
720
  end
@@ -3,83 +3,50 @@
3
3
  module Terminal
4
4
  module CSIuKeys
5
5
  class << self
6
- def [](key)
7
- return ORD[key.ord] if key.size == 1
8
- return if key[0] != "\e"
9
- return C0_LEGACY[key[1].ord] if key.size == 2 # ESC ?
10
- return csi(key) if key[1] == '['
11
- SS3[key[2].ord] if key[1] == 'O' && key.size == 3 # ESC O ?
6
+ def key_name(key)
7
+ @key_name[key.ord] if key.size == 1
12
8
  end
13
9
 
14
- private
15
-
16
- def csi(key)
17
- type = key[-1]
18
- return unicode(key) if type == 'u' # ESC [ ? u
19
- return legacy(key) if type == '~' # ESC [ ? ; <modifier> ~
20
- if key.size == 3 # ESC [ ?
21
- ord = type.ord
22
- return ord == 0x5a ? 'Shift+Tab' : SS3[ord]
23
- end
24
- return if key.size < 5
25
- return unless key.start_with?("\e[1;")
26
- # ESC [ 1 ; <modifier> ?
27
- name = SS3[type.ord] or return
28
- mod = key[4..-2]
29
- mod.empty? ? name : modifier(name, mod.to_i - 1)
30
- end
31
-
32
- def unicode(key)
33
- with_modifier(key) do |ord|
34
- ORD[ord] || UNICODE_ORD[ord] || ord.chr(Encoding::UTF_8)
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] }
35
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) }
36
23
  end
37
24
 
38
- def legacy(key)
39
- with_modifier(key) do |ord|
40
- return 'F3' if ord == 13
41
- ord == 57_427 ? 'KpBegin' : ORD[ord]
42
- end
43
- end
25
+ private
44
26
 
45
- def with_modifier(key)
46
- code, mod = key[2..-2].split(';', 2)
47
- code = yield(code.to_i) or return
48
- mod ? modifier(code, mod.to_i - 1) : code
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 # no mod
32
+ (@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
49
33
  end
50
34
 
51
- def modifier(name, mod)
52
- (MODS.filter_map { |b, n| n if mod.allbits?(b) } << name).join('+')
35
+ def with_modifier(esc)
36
+ return yield(esc[1..-2].join.to_i) if (idx = esc.index(';')).nil? # no mod
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 # no mod
39
+ (@mods.filter_map { |b, n| n if mod.allbits?(b) } << key).join('+')
53
40
  end
54
41
  end
55
42
 
56
- ORD = {
43
+ @csiu = {
57
44
  0x02 => 'Ins',
58
- 0x03 => 'Del',
59
- 0x04 => 'Del',
60
- 0x05 => 'PageUp',
61
- 0x06 => 'PageDown',
62
- 0x07 => 'Home',
63
- 0x08 => 'End',
64
45
  0x09 => 'Tab',
65
- 0x0b => 'F1',
66
- 0x0c => 'F2',
67
46
  0x0d => 'Enter',
68
- 0x0e => 'F4',
69
- 0x0f => 'F5',
70
- 0x11 => 'F6',
71
- 0x12 => 'F7',
72
- 0x13 => 'F8',
73
- 0x14 => 'F9',
74
- 0x15 => 'F10',
75
- 0x17 => 'F11',
76
- 0x18 => 'F12',
77
47
  0x1b => 'Esc',
78
- 0x7f => 'Back'
79
- }.compare_by_identity.freeze
80
-
81
- # ESC [ ? u
82
- UNICODE_ORD = {
48
+ 0x20 => 'Space',
49
+ 0x7f => 'Back',
83
50
  57_376 => 'F13',
84
51
  57_377 => 'F14',
85
52
  57_378 => 'F15',
@@ -118,7 +85,7 @@ module Terminal
118
85
  57_411 => 'KpMultiply',
119
86
  57_412 => 'KpSubtract',
120
87
  57_413 => 'KpAdd',
121
- 57_414 => 'KpEnter',
88
+ 57_414 => 'Return', # 'KpEnter'
122
89
  57_415 => 'KpEqual',
123
90
  57_416 => 'KpSeparator',
124
91
  57_417 => 'KpLeft',
@@ -160,21 +127,7 @@ module Terminal
160
127
  57_454 => 'IsoLevel5Shift'
161
128
  }.compare_by_identity.freeze
162
129
 
163
- # ESC ?
164
- C0_LEGACY = {
165
- 0x00 => 'Ctrl+Space',
166
- 0x08 => 'Ctrl+Back',
167
- 0x09 => 'Alt+Tab',
168
- 0x0d => 'Alt+Enter',
169
- 0x1b => 'Alt+Esc',
170
- 0x20 => 'Alt+Space',
171
- 0x64 => 'Alt+Del',
172
- 0x7f => 'Alt+Back'
173
- }.compare_by_identity.freeze
174
-
175
- # ESC [ ?
176
- # ESC [ O ?
177
- SS3 = {
130
+ @csi1 = {
178
131
  0x41 => 'Up', # A
179
132
  0x42 => 'Down', # B
180
133
  0x43 => 'Right', # C
@@ -187,7 +140,50 @@ module Terminal
187
140
  0x53 => 'F4' # S
188
141
  }.compare_by_identity.freeze
189
142
 
190
- MODS = {
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 = {
191
187
  1 => 'Shift',
192
188
  2 => 'Alt',
193
189
  4 => 'Ctrl',
@@ -2,11 +2,11 @@
2
2
 
3
3
  module Terminal
4
4
  module DumbKeys
5
- def self.[](key)
6
- ORD[key.ord] if key.size == 1
5
+ def self.key_name(key)
6
+ @key_name[key.ord] if key.size == 1
7
7
  end
8
8
 
9
- ORD = {
9
+ @key_name = {
10
10
  0x05 => 'Ctrl+c',
11
11
  0x08 => 'Back',
12
12
  0x09 => 'Tab',
@@ -3,55 +3,102 @@
3
3
  module Terminal
4
4
  module LegacyKeys
5
5
  class << self
6
- def [](key)
7
- return ORD[key.ord] if key.size == 1
8
- return if key[0] != "\e"
9
- return csi(key) if key[1] == '['
10
- SS3[key[2].ord] if key[1] == 'O' && key.size == 3 # ESC O ?
6
+ def key_name(key)
7
+ @key_name[key.ord] if key.size == 1
11
8
  end
12
9
 
13
- private
14
-
15
- def csi(key)
16
- return legacy(key) if key[-1] == '~' # ESC [ ? ; <modifier> ~
17
- if key.size == 3 # ESC [ ?
18
- ord = key[2].ord
19
- return ord == 0x5a ? 'Shift+Tab' : SS3[ord]
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 [ ?
20
14
  end
21
- # ESC [ 1 ; <modifier> ?
22
- return if key.size < 5
23
- return unless key.start_with?("\e[1;")
24
- # ESC [ ? {ABCDEFHPQRS}
25
- name = SS3[key[-1].ord] or return
26
- mod = key[4..-2]
27
- mod.empty? ? name : modifier(name, mod.to_i - 1)
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] == '~'
28
23
  end
29
24
 
30
- def legacy(key)
31
- with_modifier(key) { LEGACY[_1] }
32
- end
25
+ private
33
26
 
34
- def with_modifier(key)
35
- code, mod = key[2..-2].split(';', 2)
36
- code = yield(code.to_i) or return
37
- mod ? modifier(code, mod.to_i - 1) : code
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('+')
38
34
  end
39
35
 
40
- def modifier(name, mod)
41
- (MODS.filter_map { |b, n| n if mod.allbits?(b) } << name).join('+')
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
42
  end
43
43
  end
44
44
 
45
- ORD = {
46
- 0x00 => 'Ctrl+2',
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
+ 0x1b => 'Enter'
66
+ }.compare_by_identity.freeze
67
+
68
+ @ss3 = {
69
+ 0x41 => 'Up', # A
70
+ 0x42 => 'Down', # B
71
+ 0x43 => 'Right', # C
72
+ 0x44 => 'Left', # D
73
+ 0x46 => 'End', # F
74
+ 0x48 => 'Home', # H
75
+ 0x50 => 'F1', # P
76
+ 0x51 => 'F2', # Q
77
+ 0x52 => 'F3', # R
78
+ 0x53 => 'F4', # S
79
+ 0x5a => 'Shift+Tab' # Z - not widely used
80
+ }.compare_by_identity.freeze
81
+
82
+ @esc1 = {
83
+ 0x08 => 'Back',
84
+ 0x09 => 'Tab',
85
+ 0x10 => 'Ctrl+?',
86
+ 0x0d => 'Enter',
87
+ 0x1b => 'Esc',
88
+ 0x20 => 'Space',
89
+ 0x7f => 'Back'
90
+ }.compare_by_identity.freeze
91
+
92
+ @key_name = {
93
+ 0x00 => 'Ctrl+Space',
47
94
  0x01 => 'Ctrl+a',
48
95
  0x02 => 'Ctrl+b',
49
96
  0x03 => 'Ctrl+c',
50
- 0x04 => 'Del',
97
+ 0x04 => 'Ctrl+d',
51
98
  0x05 => 'Ctrl+e',
52
99
  0x06 => 'Ctrl+f',
53
100
  0x07 => 'Ctrl+g',
54
- 0x08 => 'Ctrl+h',
101
+ 0x08 => 'Ctrl+Back',
55
102
  0x09 => 'Tab',
56
103
  0x0a => 'Ctrl+j',
57
104
  0x0b => 'Ctrl+k',
@@ -70,41 +117,16 @@ module Terminal
70
117
  0x18 => 'Ctrl+x',
71
118
  0x19 => 'Ctrl+y',
72
119
  0x1a => 'Ctrl+z',
73
- 0x1d => 'Ctrl+ü', # FIND AM
74
- 0x1e => 'Ctrl+^', # FIND AM
75
- 0x1f => 'Ctrl+-',
76
120
  0x1b => 'Esc',
121
+ 0x1c => 'Ctrl+4',
122
+ 0x1d => 'Ctrl+5',
123
+ 0x1e => 'Ctrl+6',
124
+ 0x1f => 'Ctrl+7',
125
+ 0x20 => 'Space',
77
126
  0x7f => 'Back'
78
127
  }.compare_by_identity.freeze
79
128
 
80
- # ESC [ ?
81
- # ESC [ O ?
82
- SS3 = {
83
- 0x41 => 'Up', # A
84
- 0x42 => 'Down', # B
85
- 0x43 => 'Right', # C
86
- 0x44 => 'Left', # D
87
- 0x46 => 'End', # F
88
- 0x48 => 'Home', # H
89
- 0x50 => 'F1', # P
90
- 0x51 => 'F2', # Q
91
- 0x52 => 'F3', # R
92
- 0x53 => 'F4' # S
93
- }.compare_by_identity.freeze
94
-
95
- LEGACY = {
96
- 0x03 => 'Del',
97
- 0x0f => 'F5',
98
- 0x11 => 'F6',
99
- 0x12 => 'F7',
100
- 0x13 => 'F8',
101
- 0x14 => 'F9',
102
- 0x15 => 'F10',
103
- 0x17 => 'F11',
104
- 0x18 => 'F12'
105
- }.compare_by_identity.freeze
106
-
107
- MODS = {
129
+ @mods = {
108
130
  1 => 'Shift',
109
131
  2 => 'Alt',
110
132
  4 => 'Ctrl',
@@ -30,66 +30,63 @@ module Terminal
30
30
  # @return [String] key name in `:named` mode
31
31
  # @return [[String, String]] key code and key name in `:both` mode
32
32
  def read_key(mode: :named)
33
- case input_mode
34
- when :dumb
35
- read_dumb(mode)
36
- when :legacy
37
- read_legacy(mode)
38
- when :csi_u
39
- read_csi_u(mode)
40
- end
33
+ return if input_mode == :error
34
+ return read_dumb(mode == :both) if @input_mode == :dumb
35
+ return read_tty&.join if mode == :raw
36
+ key, name = @input_mode == :csi_u ? read_csiu : read_legacy
37
+ return unless key
38
+ mode == :both ? [key, name] : name || key
41
39
  end
42
40
 
43
41
  private
44
42
 
45
- def read_dumb(mode)
46
- translate_key(STDIN.getc, mode, DumbKeys)
43
+ def read_dumb(both)
44
+ inp = STDIN.getc
45
+ name = DumbKeys.key_name(inp)
46
+ both ? [inp, name] : name || inp
47
47
  rescue Interrupt
48
- translate_key("\x05", mode, DumbKeys)
49
- rescue IOError, SystemCallError
50
- @input_mode = :error
51
- nil
48
+ DumbKeys.key_name("\x05")
52
49
  end
53
50
 
54
- def read_legacy(mode) = translate_key(read_tty, mode, LegacyKeys)
55
-
56
- def read_csi_u(mode)
57
- if _write("\e[>1u")
58
- key = read_tty
59
- _write("\e[<u")
60
- translate_key(key, mode, CSIuKeys)
61
- else
62
- @input_mode = :legacy
63
- read_legacy(mode)
64
- end
65
- end
66
-
67
- def translate_key(key, mode, translator)
51
+ def read_legacy
52
+ key, *esc = read_tty
68
53
  return unless key
69
- return key if mode == :raw
70
- name = translator[key]
71
- mode == :both ? [key, name] : name || key
54
+ return key, LegacyKeys.key_name(key) if esc.empty?
55
+ [key + esc.join, LegacyKeys.translate(esc)]
72
56
  end
73
57
 
74
- def read_tty
75
- key = STDIN.getch
76
- while (nc = STDIN.read_nonblock(1, exception: false))
77
- String === nc ? key += nc : break
58
+ def read_csiu
59
+ unless @csiu_set
60
+ if _write("\e[>1u").nil?
61
+ @input_mode = :legacy
62
+ return read_legacy
63
+ end
64
+ @csiu_set = true
65
+ at_exit { _write("\e[<u") if @csiu_set }
78
66
  end
79
- key
80
- rescue Interrupt
81
- key
82
- rescue IOError, SystemCallError
83
- @input_mode = :error
84
- nil
67
+ key, *esc = read_tty
68
+ return unless key
69
+ return key, CSIuKeys.key_name(key) if esc.empty?
70
+ [key + esc.join, CSIuKeys.translate(esc)]
85
71
  end
86
72
 
87
73
  def find_input_mode
88
- return :dumb unless STDIN.tty?
89
- STDIN.sync = true
90
- return :legacy if !ansi? || _write("\e[>1u\e[?u\e[c").nil? || !csi_u?
91
- _write("\e[<u")
92
- :csi_u
74
+ case ENV['INPUT_MODE']
75
+ when 'dumb', 'DUMB'
76
+ :dumb
77
+ when 'legacy', 'LEGACY'
78
+ :legacy
79
+ when 'csi_u', 'CSI_U', 'csiu', 'CSIU'
80
+ :csi_u
81
+ else
82
+ return :dumb unless STDIN.tty?
83
+ STDIN.sync = true
84
+ return :legacy if !ansi? || _write("\e[>1u\e[?u\e[c").nil? || !csi_u?
85
+ _write("\e[<u")
86
+ :csi_u
87
+ end
88
+ rescue Interrupt
89
+ :legacy
93
90
  rescue IOError, SystemCallError
94
91
  :error
95
92
  end
@@ -98,12 +95,30 @@ module Terminal
98
95
  # iTerm2 returns result in two chunks :/
99
96
  ret = false
100
97
  while true
101
- result = read_tty or return false
102
- ret = true if result.include?("\e[?1u")
103
- break if result[-1] == 'c'
98
+ break if (str = STDIN.getch.dup) != "\e"
99
+ while (nc = STDIN.read_nonblock(1, exception: false))
100
+ String === nc ? str << nc : break
101
+ end
102
+ ret = true if str.include?("\e[?1u")
103
+ break if str[-1] == 'c'
104
104
  end
105
105
  ret
106
106
  end
107
+
108
+ # nil, nil => error; key, [] => simple key; "\e", [] => esc sequence
109
+ def read_tty
110
+ key = [STDIN.getch]
111
+ return key if key[0] != "\e"
112
+ while (nc = STDIN.read_nonblock(1, exception: false))
113
+ String === nc ? key << nc : break
114
+ end
115
+ key
116
+ rescue Interrupt
117
+ key
118
+ rescue IOError, SystemCallError
119
+ @input_mode = :error
120
+ nil
121
+ end
107
122
  end
108
123
 
109
124
  dir = __dir__
@@ -132,7 +132,7 @@ module Terminal
132
132
  0x450,
133
133
  0x451,
134
134
  0x482,
135
- 0x487,
135
+ 0x489,
136
136
  0x590,
137
137
  0x5bd,
138
138
  0x5be,
@@ -359,6 +359,7 @@ module Terminal
359
359
  0x109d,
360
360
  0x10ff,
361
361
  0x115f,
362
+ 0x11ff,
362
363
  0x135c,
363
364
  0x135f,
364
365
  0x1711,
@@ -414,8 +415,6 @@ module Terminal
414
415
  0x1a7e,
415
416
  0x1a7f,
416
417
  0x1aaf,
417
- 0x1abd,
418
- 0x1abe,
419
418
  0x1ace,
420
419
  0x1aff,
421
420
  0x1b03,
@@ -494,10 +493,6 @@ module Terminal
494
493
  0x20ab,
495
494
  0x20ac,
496
495
  0x20cf,
497
- 0x20dc,
498
- 0x20e0,
499
- 0x20e1,
500
- 0x20e4,
501
496
  0x20f0,
502
497
  0x2102,
503
498
  0x2103,
@@ -770,7 +765,7 @@ module Terminal
770
765
  0xa48f,
771
766
  0xa4c6,
772
767
  0xa66e,
773
- 0xa66f,
768
+ 0xa672,
774
769
  0xa673,
775
770
  0xa67d,
776
771
  0xa69d,
@@ -843,6 +838,10 @@ module Terminal
843
838
  0xabed,
844
839
  0xabff,
845
840
  0xd7a3,
841
+ 0xd7af,
842
+ 0xd7c6,
843
+ 0xd7ca,
844
+ 0xd7fb,
846
845
  0xdfff,
847
846
  0xf8ff,
848
847
  0xfaff,
@@ -1645,7 +1644,6 @@ module Terminal
1645
1644
  0,
1646
1645
  1,
1647
1646
  2,
1648
- 1,
1649
1647
  0,
1650
1648
  1,
1651
1649
  0,
@@ -1782,10 +1780,6 @@ module Terminal
1782
1780
  1,
1783
1781
  0,
1784
1782
  1,
1785
- 0,
1786
- 1,
1787
- 0,
1788
- 1,
1789
1783
  -1,
1790
1784
  1,
1791
1785
  -1,
@@ -2130,6 +2124,10 @@ module Terminal
2130
2124
  1,
2131
2125
  2,
2132
2126
  1,
2127
+ 0,
2128
+ 1,
2129
+ 0,
2130
+ 1,
2133
2131
  -1,
2134
2132
  2,
2135
2133
  1,
data/lib/terminal/text.rb CHANGED
@@ -114,12 +114,13 @@ module Terminal
114
114
  def char_width(char)
115
115
  ord = char.ord
116
116
  return CONTROL_CHAR_WIDTH[ord] || 2 if ord < 0x20
117
- return 1 if ord < 0xa1
118
- return @ambiguous_char_width if (size = CharWidth[ord]) == -1
119
- return size if size != 1 || char.size < 2
120
- sco = char[1].ord
121
- # Halfwidth Dakuten Handakuten
122
- sco == 0xff9e || sco == 0xff9f ? 2 : 1
117
+ return 1 if char.size < 2 && ord < 0xa1
118
+ width = CharWidth[ord]
119
+ return @ambiguous_char_width if width == -1
120
+ sco = char[-1].ord
121
+ return width if sco != 0xff9e && sco != 0xff9f
122
+ # handle halfwidth dakuten/handakuten
123
+ char.size < 2 ? 1 : char.each_char.sum { char_width(_1) }
123
124
  end
124
125
 
125
126
  def lim_pairs(snippeds, limit)
@@ -452,7 +453,7 @@ module Terminal
452
453
  SCAN_EXPR =
453
454
  /\G(?:
454
455
  (\r?\n)
455
- | (\e\[[\d;:\?]*[ABCDEFGHJKSTfminsuhl])
456
+ | (\e\[[\d;:\?]*[ABCDEFGHJKSTfhilmnsu])
456
457
  | (\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
457
458
  | (\s+)
458
459
  | (\X)
@@ -460,7 +461,7 @@ module Terminal
460
461
 
461
462
  WIDTH_SCANNER =
462
463
  /\G(?:
463
- (?:\e\[[\d;:\?]*[ABCDEFGHJKSTfminsuhl])
464
+ (?:\e\[[\d;:\?]*[ABCDEFGHJKSTfhilmnsu])
464
465
  | (?:\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
465
466
  | (\s+)
466
467
  | (\X)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.10.0'
5
+ VERSION = '0.11.0'
6
6
  end
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.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -49,7 +49,7 @@ metadata:
49
49
  rubygems_mfa_required: 'true'
50
50
  source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
51
51
  bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
52
- documentation_uri: https://rubydoc.info/gems/terminal_rb/0.10.0/Terminal
52
+ documentation_uri: https://rubydoc.info/gems/terminal_rb/0.11.0/Terminal
53
53
  rdoc_options: []
54
54
  require_paths:
55
55
  - lib
@@ -64,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
64
  - !ruby/object:Gem::Version
65
65
  version: '0'
66
66
  requirements: []
67
- rubygems_version: 3.7.0
67
+ rubygems_version: 3.7.1
68
68
  specification_version: 4
69
69
  summary: Fast terminal access with ANSI and BBCode support.
70
70
  test_files: []