terminal_rb 0.12.0 → 0.12.2
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/README.md +1 -1
- data/lib/terminal/input/{as_key_event.rb → key_event.rb} +169 -58
- data/lib/terminal/input.rb +18 -129
- data/lib/terminal/rspec/helper.rb +18 -12
- data/lib/terminal/text.rb +14 -17
- data/lib/terminal/version.rb +1 -1
- data/lib/terminal.rb +12 -10
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76a55505ad930b5c59af396e566e1079e3aa0c339d4ef68d5adf559c243c549a
|
4
|
+
data.tar.gz: 87882ec5820678e2f657d739588cd8502167aa68c5fffad47296903b387ced94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9fffe327952f951928e43e2b29aec523f87794c499b1b9c81a483a529653c4434f3422e0a32b0ed2c08a3886a50a3f74f10704a50c5397867c470cc97a974ac
|
7
|
+
data.tar.gz: fee9b96d3b899667b772fb68590ecea6476c904279b374132d30fca959f5c1cc4559709a600302cb71aeb78eb4a9b128cc7167a64efd835ddc96e93f10c5b85a
|
data/README.md
CHANGED
@@ -14,7 +14,7 @@ Terminal access with super fast ANSI control codes support and [BBCode-like](htt
|
|
14
14
|
- [NO_COLOR convention](https://no-color.org) support
|
15
15
|
- markup of text attributes and colors using a [BBCode](https://en.wikipedia.org/wiki/BBCode)-like syntax
|
16
16
|
- calculation for correct display width of strings containing Unicdode chars inclusive emojis
|
17
|
-
-
|
17
|
+
- word-wise line break generator
|
18
18
|
- supports [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
|
19
19
|
|
20
20
|
## Examples
|
@@ -1,84 +1,73 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Terminal
|
4
|
-
|
4
|
+
#
|
5
|
+
# Key event reported from {read_key_event}.
|
6
|
+
#
|
7
|
+
class KeyEvent
|
5
8
|
class << self
|
9
|
+
# @attribute [w] caching
|
10
|
+
# @return [true, false] whether KeyCodes should be cached
|
11
|
+
def caching = !!@cache
|
12
|
+
|
13
|
+
# @attribute [w] caching
|
14
|
+
def caching=(value)
|
15
|
+
if value
|
16
|
+
@cache ||= {}
|
17
|
+
else
|
18
|
+
@cache = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Translate a keyboard input string into a related KeyEvent
|
23
|
+
#
|
24
|
+
# @param [String] raw keyboard input
|
25
|
+
# @return [KeyEvent] related key event
|
6
26
|
def [](raw)
|
7
|
-
return
|
8
|
-
return
|
27
|
+
return new(raw, *@single_key[raw.ord]) if raw.size == 1
|
28
|
+
return unknown(raw) if raw[0] != "\e"
|
9
29
|
return esc1(raw, raw[1]) if raw.size == 2 # ESC ?
|
10
30
|
case raw[1]
|
11
31
|
when "\e" # ESC ESC ...
|
12
32
|
return esc_esc(raw)
|
13
33
|
when 'O'
|
14
|
-
if raw.size == 3
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
34
|
+
return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
|
35
|
+
when '['
|
36
|
+
return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
|
37
|
+
if raw.size == 6 && raw[2] == 'M' # ESC [ M b c r
|
25
38
|
return mouse_vt200(raw)
|
26
39
|
end
|
27
40
|
return csi1(raw) if raw.start_with?("\e[1;") # ESC [ 1 ; ...
|
28
41
|
case raw[-1]
|
29
|
-
when '~'
|
42
|
+
when '~' # ESC [ ... ~
|
30
43
|
return legacy(raw)
|
31
|
-
when 'u'
|
44
|
+
when 'u' # ESC [ ... u
|
32
45
|
return csi_u(raw)
|
33
|
-
when 'M'
|
46
|
+
when 'M' # ESC [ ... M
|
34
47
|
return raw[2] == '<' ? mouse_sgr(raw) : mouse_urxvt(raw)
|
35
|
-
when 'm'
|
48
|
+
when 'm' # ESC [ ... m
|
36
49
|
return mouse_sgr(raw) if raw[2] == '<'
|
37
50
|
end
|
38
51
|
end
|
39
|
-
|
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 })
|
52
|
+
unknown(raw)
|
47
53
|
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
+
# @!visibility private
|
56
|
+
def new(raw, key = raw, modifier = 0, extra = nil)
|
57
|
+
@cache ? (@cache[raw] ||= super.freeze) : super.freeze
|
55
58
|
end
|
56
59
|
|
57
|
-
|
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
|
60
|
+
private
|
63
61
|
|
64
|
-
def
|
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
|
62
|
+
def unknown(raw) = new(raw, nil)
|
74
63
|
|
75
64
|
def csi_u(raw)
|
76
65
|
# ESC [ <code> u
|
77
66
|
# ESC [ <code> ; <modifier> u
|
78
|
-
return
|
67
|
+
return unknown(raw) if raw.size < 4
|
79
68
|
idx = raw.index(';')
|
80
69
|
code = raw[2..(idx ? idx - 1 : -2)].to_i
|
81
|
-
|
70
|
+
new(
|
82
71
|
raw,
|
83
72
|
@csiu[code] || code.chr(Encoding::UTF_8),
|
84
73
|
idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
|
@@ -88,9 +77,9 @@ module Terminal
|
|
88
77
|
def legacy(raw)
|
89
78
|
# ESC [ <code> ~
|
90
79
|
# ESC [ <code> ; <modifier> ~
|
91
|
-
return
|
80
|
+
return unknown(raw) if raw.size < 4
|
92
81
|
idx = raw.index(';')
|
93
|
-
|
82
|
+
new(
|
94
83
|
raw,
|
95
84
|
@csi[raw[2..(idx ? idx - 1 : -2)].to_i],
|
96
85
|
idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
|
@@ -100,26 +89,135 @@ module Terminal
|
|
100
89
|
def csi1(raw)
|
101
90
|
# ESC [ 1 ; [~ABCDEFHPQRS]
|
102
91
|
# ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
|
103
|
-
return
|
92
|
+
return unknown(raw) if raw.size < 5
|
104
93
|
key, modifier = @ss3[raw[-1].ord]
|
105
94
|
modifier ||= 0
|
106
|
-
return
|
95
|
+
return new(raw, nil, modifier) unless key
|
107
96
|
modifier |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
|
108
|
-
|
97
|
+
new(raw, key, modifier)
|
109
98
|
end
|
110
99
|
|
111
100
|
def esc_esc(raw)
|
112
101
|
return esc1(raw, raw[2]) if raw.size == 3 # ESC ESC ?
|
113
102
|
ret = self[raw[1..]]
|
114
|
-
|
103
|
+
new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
|
115
104
|
end
|
116
105
|
|
117
106
|
def esc1(raw, char)
|
118
107
|
key, modifier = @esc1[char.ord]
|
119
|
-
|
108
|
+
new(raw, key || char, modifier || 2)
|
109
|
+
end
|
110
|
+
|
111
|
+
def mouse_vt200(raw)
|
112
|
+
# ESC [ M b c r
|
113
|
+
mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
|
114
|
+
end
|
115
|
+
|
116
|
+
def mouse_sgr(raw)
|
117
|
+
# ESC [ < <code> ; <col> ; <row> M
|
118
|
+
# ESC [ < <code> ; <col> ; <row> m
|
119
|
+
return unknown(raw) if raw.size < 8
|
120
|
+
bcr = raw[3..-2].split(';', 3).map!(&:to_i)
|
121
|
+
bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
|
122
|
+
end
|
123
|
+
|
124
|
+
def mouse_urxvt(raw)
|
125
|
+
# ESC [ <code> ; <col> ; <row> M
|
126
|
+
return unknown(raw) if raw.size < 8
|
127
|
+
bcr = raw[2..-2].split(';', 3).map!(&:to_i)
|
128
|
+
bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
|
129
|
+
end
|
130
|
+
|
131
|
+
def mouse_event(raw, btn, col, row)
|
132
|
+
return unknown(raw) if btn < 0 || col < 1 || row < 1
|
133
|
+
key, btn = kind_of_mouse_event(btn)
|
134
|
+
modifier = btn.allbits?(4) ? 1 : 0
|
135
|
+
modifier += 2 if btn.allbits?(8)
|
136
|
+
modifier += 4 if btn.allbits?(16)
|
137
|
+
new(raw, key, modifier, [col, row])
|
138
|
+
end
|
139
|
+
|
140
|
+
def mouse_event_key(btn)
|
141
|
+
kind = btn & 3
|
142
|
+
key = (btn >= 64 ? @mouse_wheel_kind : @mouse_kind)[kind]
|
143
|
+
# TODO: what about btn >= 128 for more mouse wheel events?
|
144
|
+
[btn - kind, key]
|
120
145
|
end
|
121
146
|
end
|
122
147
|
|
148
|
+
# Event string received from standard input.
|
149
|
+
# This can be a simple value like `"a"`or more complex input like
|
150
|
+
# `"\e[24;6~"` (for Shift+Ctrl+F12).
|
151
|
+
#
|
152
|
+
# @return [String] received event string
|
153
|
+
attr_reader :raw
|
154
|
+
|
155
|
+
# Pressed key without any modifiers.
|
156
|
+
# This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
|
157
|
+
# @return [String, Symbol] key without modifiers
|
158
|
+
attr_reader :key
|
159
|
+
|
160
|
+
# Modifier key code. This represents the encoded key modifier like `Shift`
|
161
|
+
# or `Alt`.
|
162
|
+
#
|
163
|
+
# @return [Integer] modifier key code
|
164
|
+
attr_reader :modifier
|
165
|
+
|
166
|
+
# @comment for mouse events
|
167
|
+
# @!visibility private
|
168
|
+
attr_reader :extra
|
169
|
+
|
170
|
+
# Name of the key event.
|
171
|
+
# This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
|
172
|
+
# keys.
|
173
|
+
#
|
174
|
+
# @return [String] key name
|
175
|
+
attr_reader :name
|
176
|
+
|
177
|
+
# @attribute [r] modifier?
|
178
|
+
# @return [true, false] whether a key modifier was pressed
|
179
|
+
def modifier? = @modifier != 0
|
180
|
+
|
181
|
+
# @attribute [r] simple?
|
182
|
+
# @return [true, false] whether a simple key was pressed
|
183
|
+
def simple? = @raw == @name
|
184
|
+
|
185
|
+
# All pressed keys.
|
186
|
+
# This is composed by all {modifier} and the {key}.
|
187
|
+
#
|
188
|
+
# @return [Array<Symbol, String>] all pressed keys
|
189
|
+
def to_a = @ary.dup
|
190
|
+
|
191
|
+
# @!visibility private
|
192
|
+
def to_ary = simple? ? [@raw] : [@raw, @name]
|
193
|
+
|
194
|
+
# @!visibility private
|
195
|
+
def to_s = @name.dup
|
196
|
+
|
197
|
+
# @!visibility private
|
198
|
+
def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
|
199
|
+
|
200
|
+
# @!visibility private
|
201
|
+
def freeze
|
202
|
+
@raw.freeze
|
203
|
+
@key.freeze
|
204
|
+
@extra.freeze
|
205
|
+
@name.freeze
|
206
|
+
super
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def initialize(raw, key, modifier, extra)
|
212
|
+
@raw = raw
|
213
|
+
@key = key
|
214
|
+
@modifier = modifier
|
215
|
+
@extra = extra
|
216
|
+
@ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
|
217
|
+
@ary << key if key
|
218
|
+
@name = @ary.join('+') # .encode(Encoding::UTF_8)
|
219
|
+
end
|
220
|
+
|
123
221
|
@mouse_kind = %i[
|
124
222
|
MButton1
|
125
223
|
MButton2
|
@@ -312,6 +410,19 @@ module Terminal
|
|
312
410
|
0x20 => :Space,
|
313
411
|
0x7f => :Back
|
314
412
|
}.compare_by_identity.freeze
|
413
|
+
|
414
|
+
@cache = {}
|
415
|
+
|
416
|
+
MODIFIERS = {
|
417
|
+
1 => :Shift,
|
418
|
+
2 => :Alt,
|
419
|
+
4 => :Ctrl,
|
420
|
+
8 => :Super,
|
421
|
+
16 => :Hyper,
|
422
|
+
32 => :Meta,
|
423
|
+
64 => :Caps,
|
424
|
+
128 => :Num
|
425
|
+
}.freeze
|
426
|
+
private_constant :MODIFIERS
|
315
427
|
end
|
316
|
-
private_constant :AsKeyEvent
|
317
428
|
end
|
data/lib/terminal/input.rb
CHANGED
@@ -9,7 +9,8 @@ module Terminal
|
|
9
9
|
# @attribute [r] input_mode
|
10
10
|
#
|
11
11
|
# @return [:csi_u]
|
12
|
-
# when CSIu protocol
|
12
|
+
# when [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
|
13
|
+
# supported
|
13
14
|
# @return [:legacy]
|
14
15
|
# for standard terminal
|
15
16
|
# @return [:dumb]
|
@@ -25,13 +26,13 @@ module Terminal
|
|
25
26
|
# The input will be returned as named key codes like "Ctrl+c" by default.
|
26
27
|
# This can be changed by `mode`.
|
27
28
|
#
|
28
|
-
# @deprecated Use
|
29
|
+
# @deprecated Use rather {read_key_event} for better input handling.
|
29
30
|
#
|
30
31
|
# @param [:named, :raw, :both] mode modifies the result
|
31
32
|
# @return [String] key code ("as is") in `:raw` mode
|
32
33
|
# @return [String] key name in `:named` mode
|
33
34
|
# @return [[String, String]] key code and key name in `:both` mode
|
34
|
-
# @return [nil] in
|
35
|
+
# @return [nil] in error case
|
35
36
|
def read_key(mode: :named)
|
36
37
|
event = read_key_event or return
|
37
38
|
return event.raw if mode == :raw
|
@@ -42,7 +43,7 @@ module Terminal
|
|
42
43
|
# Read next {KeyEvent} from standard input.
|
43
44
|
#
|
44
45
|
# @return [KeyEvent] next event
|
45
|
-
# @return [nil]
|
46
|
+
# @return [nil] in error case
|
46
47
|
def read_key_event
|
47
48
|
case input_mode
|
48
49
|
when :dumb
|
@@ -51,25 +52,24 @@ module Terminal
|
|
51
52
|
KeyEvent.new(raw, *opts)
|
52
53
|
when :csi_u, :legacy
|
53
54
|
# raw = with_mouse ? read_tty_with_mouse : read_tty
|
54
|
-
raw = read_tty
|
55
|
-
AsKeyEvent[raw] if raw
|
55
|
+
KeyEvent[raw] if (raw = read_tty)
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
61
|
def find_input_mode
|
62
|
+
# order is important!
|
62
63
|
im = ENV['INPUT_MODE']
|
63
64
|
return :dumb if im == 'dumb'
|
64
65
|
return :legacy if im == 'legacy'
|
65
66
|
return :dumb unless STDIN.tty?
|
66
67
|
if ansi? && _write("\e[>1u\e[?u\e[c") && csi_u?
|
67
68
|
at_exit { _write("\e[<u") }
|
68
|
-
:csi_u
|
69
|
-
else
|
70
|
-
:legacy
|
69
|
+
return :csi_u
|
71
70
|
end
|
72
|
-
|
71
|
+
:legacy
|
72
|
+
rescue Interrupt
|
73
73
|
:legacy
|
74
74
|
rescue IOError, SystemCallError
|
75
75
|
:error
|
@@ -90,7 +90,7 @@ module Terminal
|
|
90
90
|
nil
|
91
91
|
end
|
92
92
|
|
93
|
-
def read_tty_with_mouse(
|
93
|
+
def read_tty_with_mouse(each_move: false)
|
94
94
|
# highlight: '1001'
|
95
95
|
# drag: '1002'
|
96
96
|
# move: '1003'
|
@@ -98,7 +98,7 @@ module Terminal
|
|
98
98
|
# sgr: '1006'
|
99
99
|
# urxvt: '1015'
|
100
100
|
# pixel: '1016'
|
101
|
-
opts =
|
101
|
+
opts = each_move ? '1000;1003;1006;1015' : '1000;1006;1015'
|
102
102
|
opts = _write("\e[?#{opts}h") ? "\e[?#{opts}l" : nil
|
103
103
|
read_tty
|
104
104
|
ensure
|
@@ -106,13 +106,13 @@ module Terminal
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def read_tty
|
109
|
-
key = STDIN.getch
|
110
|
-
|
111
|
-
|
112
|
-
|
109
|
+
if (key = STDIN.getch) == "\e"
|
110
|
+
while (nc = STDIN.read_nonblock(1, exception: false))
|
111
|
+
String === nc ? key << nc : break
|
112
|
+
end
|
113
113
|
end
|
114
114
|
key
|
115
|
-
rescue
|
115
|
+
rescue Interrupt
|
116
116
|
nil
|
117
117
|
rescue IOError, SystemCallError
|
118
118
|
@input_mode = :error
|
@@ -130,116 +130,5 @@ module Terminal
|
|
130
130
|
private_constant :DUMB_KEYS
|
131
131
|
end
|
132
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
|
241
|
-
end
|
242
|
-
|
243
|
-
autoload :AsKeyEvent, "#{__dir__}/input/as_key_event.rb"
|
244
|
-
private_constant :AsKeyEvent
|
133
|
+
autoload :KeyEvent, "#{__dir__}/input/key_event.rb"
|
245
134
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, colors: 256, size: [25, 80], pos: [1, 1]|
|
4
4
|
let(:stdout) { [] }
|
5
|
+
let(:stdin) { [] }
|
5
6
|
let(:stdoutstr) { stdout.join }
|
6
7
|
|
7
8
|
before do
|
@@ -9,21 +10,22 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
|
|
9
10
|
allow(Terminal).to receive(:application).with(no_args).and_return(
|
10
11
|
application
|
11
12
|
)
|
13
|
+
allow(Terminal).to receive(:input_mode).with(no_args).and_return(
|
14
|
+
ansi ? :csi_u : :dumb
|
15
|
+
)
|
12
16
|
allow(Terminal).to receive(:colors).with(no_args).and_return(
|
13
17
|
colors == :true_color ? 16_777_216 : colors
|
14
18
|
)
|
15
19
|
allow(Terminal).to receive(:size).with(no_args).and_return(size)
|
16
20
|
allow(Terminal).to receive(:pos).with(no_args).and_return(pos)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
nobbc = ->(s) { Terminal::Ansi.undecorate(s) }
|
26
|
-
end
|
21
|
+
|
22
|
+
bbcode_ = {
|
23
|
+
true =>
|
24
|
+
ansi ?
|
25
|
+
->(s) { Terminal::Ansi.bbcode(s) } :
|
26
|
+
->(s) { Terminal::Ansi.plain(s) },
|
27
|
+
false => ansi ? lambda(&:to_s) : ->(s) { Terminal::Ansi.undecorate(s) }
|
28
|
+
}.compare_by_identity
|
27
29
|
|
28
30
|
allow(Terminal).to receive(:<<) do |object|
|
29
31
|
stdout.push(bbcode[object]) unless object.nil?
|
@@ -31,7 +33,7 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
|
|
31
33
|
end
|
32
34
|
|
33
35
|
allow(Terminal).to receive(:print) do |*objects, bbcode: true|
|
34
|
-
bbcode = bbcode
|
36
|
+
bbcode = bbcode_[bbcode]
|
35
37
|
objects.flatten.each { stdout.push(bbcode[_1]) unless _1.nil? }
|
36
38
|
nil
|
37
39
|
end
|
@@ -42,7 +44,7 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
|
|
42
44
|
stdout.push("\n")
|
43
45
|
next
|
44
46
|
end
|
45
|
-
bbcode = bbcode
|
47
|
+
bbcode = bbcode_[bbcode]
|
46
48
|
objects.each do |s|
|
47
49
|
next stdout.push("\n") if s.nil?
|
48
50
|
stdout.push(s = bbcode[s])
|
@@ -55,5 +57,9 @@ RSpec.shared_context 'with Terminal.rb' do |ansi: true, application: :kitty, col
|
|
55
57
|
stdout.push(object = object.to_s)
|
56
58
|
object.bytesize
|
57
59
|
end
|
60
|
+
|
61
|
+
allow(Terminal).to receive(:read_key_event) do
|
62
|
+
Terminal::KeyEvent[stdin.shift || raise('stdin buffer is empty')]
|
63
|
+
end
|
58
64
|
end
|
59
65
|
end
|
data/lib/terminal/text.rb
CHANGED
@@ -30,7 +30,7 @@ module Terminal
|
|
30
30
|
def width(str, bbcode: true)
|
31
31
|
str = bbcode ? Ansi.unbbcode(str) : str.to_s
|
32
32
|
return 0 if str.empty?
|
33
|
-
str = str.encode(
|
33
|
+
str = str.encode(@encoding) if str.encoding != @encoding
|
34
34
|
width = 0
|
35
35
|
str.scan(WIDTH_SCANNER) do |sp, gc|
|
36
36
|
next width += char_width(gc) if gc
|
@@ -113,7 +113,7 @@ module Terminal
|
|
113
113
|
|
114
114
|
def char_width(char)
|
115
115
|
ord = char.ord
|
116
|
-
return
|
116
|
+
return @ctrlchar_width[ord] || 2 if ord < 0x20
|
117
117
|
return 1 if char.size < 2 && ord < 0xa1
|
118
118
|
width = CharWidth[ord]
|
119
119
|
return @ambiguous_char_width if width == -1
|
@@ -124,7 +124,7 @@ module Terminal
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def lim_pairs(snippeds, limit)
|
127
|
-
line =
|
127
|
+
line = @empty.dup
|
128
128
|
size = 0
|
129
129
|
csi = nil
|
130
130
|
snippeds.each do |snipped|
|
@@ -144,7 +144,7 @@ module Terminal
|
|
144
144
|
|
145
145
|
if snipped == :hard_nl
|
146
146
|
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
147
|
-
line =
|
147
|
+
line = @empty.dup
|
148
148
|
csi = nil
|
149
149
|
next size = 0
|
150
150
|
end
|
@@ -190,7 +190,7 @@ module Terminal
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def pairs(snippeds)
|
193
|
-
line =
|
193
|
+
line = @empty.dup
|
194
194
|
size = 0
|
195
195
|
csi = nil
|
196
196
|
snippeds.each do |snipped|
|
@@ -208,7 +208,7 @@ module Terminal
|
|
208
208
|
|
209
209
|
if snipped == :hard_nl
|
210
210
|
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
211
|
-
line =
|
211
|
+
line = @empty.dup
|
212
212
|
csi = nil
|
213
213
|
next size = 0
|
214
214
|
end
|
@@ -229,7 +229,7 @@ module Terminal
|
|
229
229
|
end
|
230
230
|
|
231
231
|
def lim_lines(snippeds, limit)
|
232
|
-
line =
|
232
|
+
line = @empty.dup
|
233
233
|
size = 0
|
234
234
|
csi = nil
|
235
235
|
snippeds.each do |snipped|
|
@@ -249,7 +249,7 @@ module Terminal
|
|
249
249
|
|
250
250
|
if snipped == :hard_nl
|
251
251
|
yield(line[-1] == ' ' ? line.chop : line)
|
252
|
-
line =
|
252
|
+
line = @empty.dup
|
253
253
|
csi = nil
|
254
254
|
next size = 0
|
255
255
|
end
|
@@ -295,7 +295,7 @@ module Terminal
|
|
295
295
|
end
|
296
296
|
|
297
297
|
def lines(snippeds)
|
298
|
-
line =
|
298
|
+
line = @empty.dup
|
299
299
|
size = 0
|
300
300
|
csi = nil
|
301
301
|
snippeds.each do |snipped|
|
@@ -313,7 +313,7 @@ module Terminal
|
|
313
313
|
|
314
314
|
if snipped == :hard_nl
|
315
315
|
yield(line[-1] == ' ' ? line.chop : line)
|
316
|
-
line =
|
316
|
+
line = @empty.dup
|
317
317
|
csi = nil
|
318
318
|
next size = 0
|
319
319
|
end
|
@@ -342,7 +342,7 @@ module Terminal
|
|
342
342
|
next ret << (last = :hard_nl)
|
343
343
|
end
|
344
344
|
|
345
|
-
txt = txt.encode(
|
345
|
+
txt = txt.encode(@encoding) if txt.encoding != @encoding
|
346
346
|
|
347
347
|
txt.scan(SCAN_EXPR) do |nl, csi, osc, space, gc|
|
348
348
|
if gc
|
@@ -445,10 +445,8 @@ module Terminal
|
|
445
445
|
private_constant :Osc, :CsiEnd, :Csi, :Word, :WordEx
|
446
446
|
|
447
447
|
@ambiguous_char_width = 1
|
448
|
-
|
449
|
-
|
450
|
-
EMPTY = String.new(encoding: ENC).freeze
|
451
|
-
private_constant :ENC, :EMPTY
|
448
|
+
@encoding = Encoding::UTF_8
|
449
|
+
@empty = String.new(encoding: @encoding).freeze
|
452
450
|
|
453
451
|
SCAN_EXPR =
|
454
452
|
/\G(?:
|
@@ -468,7 +466,7 @@ module Terminal
|
|
468
466
|
)/x
|
469
467
|
private_constant :SCAN_EXPR, :WIDTH_SCANNER
|
470
468
|
|
471
|
-
|
469
|
+
@ctrlchar_width = {
|
472
470
|
0x00 => 0,
|
473
471
|
0x01 => 1,
|
474
472
|
0x02 => 1,
|
@@ -502,7 +500,6 @@ module Terminal
|
|
502
500
|
0x1e => 1,
|
503
501
|
0x1f => 1
|
504
502
|
}.compare_by_identity.freeze
|
505
|
-
private_constant :CONTROL_CHAR_WIDTH
|
506
503
|
|
507
504
|
autoload :CharWidth, "#{__dir__}/text/char_width.rb"
|
508
505
|
private_constant :CharWidth
|
data/lib/terminal/version.rb
CHANGED
data/lib/terminal.rb
CHANGED
@@ -11,16 +11,16 @@ require_relative 'terminal/input'
|
|
11
11
|
# It automagically detects whether your terminal supports ANSI features, like
|
12
12
|
# coloring (see {colors}) or the
|
13
13
|
# [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol) support
|
14
|
-
# (see {
|
15
|
-
# chars (see {Text.width}) and help
|
16
|
-
# (see {Text.each_line}).
|
14
|
+
# (see {read_key_event} and {input_mode}).
|
15
|
+
# It calculates the display width for Unicode chars (see {Text.width}) and help
|
16
|
+
# you to display text with line formatting (see {Text.each_line}).
|
17
17
|
#
|
18
18
|
module Terminal
|
19
19
|
class << self
|
20
|
-
# Return true
|
21
|
-
# When the terminal does not support it, {colors} will return `2`
|
22
|
-
#
|
23
|
-
#
|
20
|
+
# Return true if the current terminal supports ANSI control codes.
|
21
|
+
# When the terminal does not support it, {colors} will return `2` and all
|
22
|
+
# output methods ({<<}, {print}, {puts}) will not forward ANSI control
|
23
|
+
# codes to the terminal, {read_key_event} will not support CSIu.
|
24
24
|
#
|
25
25
|
# @attribute [r] ansi?
|
26
26
|
# @return [Boolean] whether ANSI control codes are supported
|
@@ -123,8 +123,9 @@ module Terminal
|
|
123
123
|
|
124
124
|
# Screen size as a tuple of {rows} and {columns}.
|
125
125
|
#
|
126
|
-
# If the terminal does not support the report
|
127
|
-
# is not supported in general then environment variables
|
126
|
+
# If the terminal does not support the report of it's dimension or ANSI
|
127
|
+
# is not supported in general then environment variables `COLUMNS` and
|
128
|
+
# `LINES` will be used.
|
128
129
|
# If this failed `[25, 80]` will be returned as default.
|
129
130
|
#
|
130
131
|
# Setting the terminal size is not widely supported.
|
@@ -156,7 +157,8 @@ module Terminal
|
|
156
157
|
# Hide the cursor.
|
157
158
|
# Will not send the control code if the cursor is already hidden.
|
158
159
|
#
|
159
|
-
#
|
160
|
+
# When you called {hide_cursor} n-times you need to call {show_cursor}
|
161
|
+
# n-times to show the cursor again.
|
160
162
|
#
|
161
163
|
# @return [Terminal] itself
|
162
164
|
def hide_cursor
|
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.12.
|
4
|
+
version: 0.12.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Blumtritt
|
@@ -9,9 +9,8 @@ bindir: bin
|
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
|
-
description:
|
13
|
-
|
14
|
-
BBCode-like embedded text attribute syntax.
|
12
|
+
description: 'Terminal access with super fast ANSI control codes support, modern CSIu
|
13
|
+
input, word-wise line break, BBCode-like embedded text attribute syntax. '
|
15
14
|
executables:
|
16
15
|
- bbcode
|
17
16
|
extensions: []
|
@@ -34,7 +33,7 @@ files:
|
|
34
33
|
- lib/terminal/ansi/named_colors.rb
|
35
34
|
- lib/terminal/detect.rb
|
36
35
|
- lib/terminal/input.rb
|
37
|
-
- lib/terminal/input/
|
36
|
+
- lib/terminal/input/key_event.rb
|
38
37
|
- lib/terminal/rspec/helper.rb
|
39
38
|
- lib/terminal/text.rb
|
40
39
|
- lib/terminal/text/char_width.rb
|
@@ -44,11 +43,11 @@ homepage: https://codeberg.org/mblumtritt/Terminal.rb
|
|
44
43
|
licenses:
|
45
44
|
- MIT
|
46
45
|
metadata:
|
47
|
-
rubygems_mfa_required: 'true'
|
48
|
-
yard.run: yard
|
49
46
|
source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
|
50
47
|
bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
|
51
|
-
documentation_uri: https://rubydoc.info/gems/terminal_rb
|
48
|
+
documentation_uri: https://rubydoc.info/gems/terminal_rb
|
49
|
+
rubygems_mfa_required: 'true'
|
50
|
+
yard.run: yard
|
52
51
|
rdoc_options: []
|
53
52
|
require_paths:
|
54
53
|
- lib
|
@@ -65,5 +64,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements: []
|
66
65
|
rubygems_version: 3.7.1
|
67
66
|
specification_version: 4
|
68
|
-
summary: Fast terminal access with ANSI
|
67
|
+
summary: Fast terminal access with ANSI, CSIu, BBCode, word-wise line break support.
|
69
68
|
test_files: []
|