terminal_rb 0.19.0 → 1.0.3
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 +5 -4
- data/bin/bbcode +25 -21
- data/examples/24bit-colors.rb +11 -12
- data/examples/3bit-colors.rb +9 -9
- data/examples/8bit-colors.rb +18 -18
- data/examples/attributes.rb +24 -10
- data/examples/bbcode.rb +23 -19
- data/examples/info.rb +7 -7
- data/examples/key-codes.rb +4 -7
- data/examples/screen_viewer.rb +82 -0
- data/examples/text.rb +12 -28
- data/lib/terminal/ansi/named_colors.rb +1 -0
- data/lib/terminal/ansi/screen_viewer.rb +224 -0
- data/lib/terminal/ansi.rb +502 -484
- data/lib/terminal/detect.rb +1 -0
- data/lib/terminal/input/ansi.rb +18 -16
- data/lib/terminal/input/dumb.rb +5 -8
- data/lib/terminal/input/key_event.rb +131 -75
- data/lib/terminal/input.rb +55 -44
- data/lib/terminal/output/ansi.rb +44 -6
- data/lib/terminal/output/dumb.rb +33 -0
- data/lib/terminal/output.rb +144 -122
- data/lib/terminal/rspec/helper.rb +30 -1
- data/lib/terminal/shell.rb +10 -6
- data/lib/terminal/text/char_width.rb +178 -176
- data/lib/terminal/text/formatter.rb +619 -0
- data/lib/terminal/text.rb +167 -423
- data/lib/terminal/version.rb +1 -1
- data/lib/terminal.rb +79 -75
- metadata +9 -7
- data/terminal_rb.gemspec +0 -36
data/lib/terminal/detect.rb
CHANGED
data/lib/terminal/input/ansi.rb
CHANGED
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Terminal
|
|
4
|
+
# @private
|
|
4
5
|
module AnsiInput
|
|
5
6
|
attr_reader :input_mode
|
|
6
7
|
|
|
7
8
|
def on_key_event(mouse: false, mouse_move: false, focus: false)
|
|
8
9
|
raise('already reading key events') if (@in_recursion += 1) != 1
|
|
9
10
|
return unless block_given?
|
|
10
|
-
opts =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
opts = __input_option(mouse, mouse_move, focus)
|
|
12
|
+
STDIN.noecho do
|
|
13
|
+
opts &&= @in.syswrite("#{opts}h") ? "#{opts}l" : nil
|
|
14
|
+
while (raw = @in.getch)
|
|
15
|
+
next yield(KeyEvent[raw]) if raw != "\e"
|
|
16
|
+
while String === (nc = @in.read_nonblock(1, exception: false))
|
|
17
|
+
raw << nc
|
|
18
|
+
end
|
|
19
|
+
next yield(KeyEvent[raw]) if raw.rindex("\e") < 2
|
|
20
|
+
raw[0] = ''
|
|
21
|
+
raw.split("\e").each { yield(KeyEvent["\e#{it}"]) }
|
|
18
22
|
end
|
|
19
|
-
(yield(KeyEvent[raw]) ? next : break) if lesci < 2
|
|
20
|
-
break unless raw[1..].split("\e").all? { yield(KeyEvent["\e#{_1}"]) }
|
|
21
23
|
end
|
|
22
24
|
rescue IOError, SystemCallError
|
|
23
25
|
__input_error
|
|
24
26
|
false
|
|
25
27
|
ensure
|
|
26
28
|
@in_recursion -= 1
|
|
27
|
-
@in
|
|
29
|
+
@in.syswrite(opts) if opts && @in
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
private
|
|
31
33
|
|
|
32
|
-
def
|
|
33
|
-
opts =
|
|
34
|
+
def __input_option(mouse, mouse_move, focus)
|
|
35
|
+
opts = (mouse ? ['1000'] : [])
|
|
34
36
|
# highlight: '1001'
|
|
35
37
|
# drag: '1002'
|
|
36
|
-
opts << '
|
|
37
|
-
opts << '
|
|
38
|
+
opts << '1003' if mouse_move
|
|
39
|
+
opts << '1004' if focus
|
|
38
40
|
# ext: '1005'
|
|
39
41
|
# sgr: '1006'
|
|
40
42
|
# urxvt: '1015'
|
|
41
43
|
# pixel: '1016'
|
|
42
|
-
"\e[?#{opts};1006" unless opts.empty?
|
|
44
|
+
"\e[?#{opts.join(';')};1006" unless opts.empty?
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
|
data/lib/terminal/input/dumb.rb
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Terminal
|
|
4
|
+
# @private
|
|
4
5
|
module DumpInput
|
|
5
6
|
def input_mode = :dumb
|
|
6
7
|
|
|
7
8
|
def on_key_event(*_)
|
|
8
9
|
raise('already reading key events') if (@in_recursion += 1) != 1
|
|
9
10
|
return unless block_given?
|
|
10
|
-
while (raw =
|
|
11
|
+
while (raw = STDIN.getc)
|
|
11
12
|
opts = dumb_keys[raw.ord] if raw.size == 1
|
|
12
|
-
|
|
13
|
+
yield(KeyEvent.new(raw, *opts))
|
|
13
14
|
end
|
|
15
|
+
rescue Interrupt
|
|
16
|
+
yield(KeyEvent.new("\x05", 'c', 4))
|
|
14
17
|
rescue IOError, SystemCallError
|
|
15
18
|
__input_error
|
|
16
19
|
false
|
|
@@ -30,12 +33,6 @@ module Terminal
|
|
|
30
33
|
0x1b => :Esc
|
|
31
34
|
}.compare_by_identity.freeze
|
|
32
35
|
end
|
|
33
|
-
|
|
34
|
-
def __getc
|
|
35
|
-
STDIN.getc
|
|
36
|
-
rescue Interrupt
|
|
37
|
-
"\x05"
|
|
38
|
-
end
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
private_constant :DumpInput
|
|
@@ -1,68 +1,95 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Terminal
|
|
4
|
+
# Represents a keyboard, mouse, or focus event read from the terminal.
|
|
4
5
|
#
|
|
5
|
-
#
|
|
6
|
+
# Instances are immutable (frozen) and may be cached for performance.
|
|
6
7
|
#
|
|
8
|
+
# @see Terminal.on_key_event
|
|
9
|
+
# @see Terminal.read_key_event
|
|
10
|
+
#
|
|
11
|
+
# @example Parse a raw escape sequence
|
|
12
|
+
# event = Terminal::KeyEvent["\r"]
|
|
13
|
+
# event.key # => :Enter
|
|
14
|
+
# event.name # => "Enter"
|
|
15
|
+
# event.simple? # => true
|
|
16
|
+
#
|
|
17
|
+
# @example Inspect modifier keys
|
|
18
|
+
# event = Terminal::KeyEvent["\e[1;5A"]
|
|
19
|
+
# event.key # => :Up
|
|
20
|
+
# event.name # => "Ctrl+Up"
|
|
21
|
+
# event.modifier? # => true
|
|
22
|
+
# event.modifiers # => [:Ctrl]
|
|
7
23
|
class KeyEvent
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
24
|
+
# The key identifier.
|
|
25
|
+
#
|
|
26
|
+
# @return [Symbol, String, nil] a named key like +:Enter+, +:Tab+,
|
|
27
|
+
# +:MBLeft+ (mouse), or a character string like +"a"+; +nil+ for
|
|
28
|
+
# unknown sequences
|
|
12
29
|
attr_reader :key
|
|
13
30
|
|
|
14
|
-
#
|
|
15
|
-
# or `Alt`.
|
|
31
|
+
# Bitmask of active modifier keys.
|
|
16
32
|
#
|
|
17
|
-
# @
|
|
18
|
-
|
|
33
|
+
# @see #modifiers
|
|
34
|
+
# @see #modifier?
|
|
35
|
+
#
|
|
36
|
+
# @return [Integer] modifier_bits bitmask
|
|
37
|
+
attr_reader :modifier_bits
|
|
19
38
|
|
|
39
|
+
# Names of active modifier keys.
|
|
40
|
+
#
|
|
41
|
+
# @see #modifier?
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<Symbol>] modifier names
|
|
44
|
+
attr_reader :modifiers
|
|
45
|
+
|
|
46
|
+
# Whether any modifier keys are active.
|
|
47
|
+
#
|
|
48
|
+
# @see #modifiers
|
|
49
|
+
#
|
|
20
50
|
# @attribute [r] modifier?
|
|
21
|
-
# @return [true, false]
|
|
22
|
-
def modifier? = @
|
|
51
|
+
# @return [true, false]
|
|
52
|
+
def modifier? = @modifier_bits != 0
|
|
23
53
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
54
|
+
# Human-readable name including modifier and key.
|
|
55
|
+
#
|
|
56
|
+
# @example
|
|
57
|
+
# Terminal::KeyEvent["\e[1;5A"].name # => "Ctrl+Up"
|
|
27
58
|
#
|
|
28
|
-
# @return [String]
|
|
59
|
+
# @return [String] e.g. `"Ctrl+Alt+a"` or `"Enter"`
|
|
29
60
|
attr_reader :name
|
|
30
61
|
|
|
31
|
-
# Mouse event
|
|
62
|
+
# Mouse position when this is a mouse event.
|
|
32
63
|
#
|
|
33
|
-
# @return [
|
|
34
|
-
#
|
|
64
|
+
# @return [Array<Integer, Integer>, nil] +[row, column]+ for mouse
|
|
65
|
+
# events, +nil+ for keyboard events
|
|
35
66
|
attr_reader :position
|
|
36
67
|
|
|
37
|
-
#
|
|
38
|
-
# This can be a simple value like `"a"`or more complex input like
|
|
39
|
-
# `"\e[24;6~"` (for Shift+Ctrl+F12).
|
|
68
|
+
# The raw input sequence as received from the terminal.
|
|
40
69
|
#
|
|
41
|
-
# @return [String]
|
|
70
|
+
# @return [String]
|
|
42
71
|
attr_reader :raw
|
|
43
72
|
|
|
73
|
+
# Whether the raw input equals the name (no modifier, no special
|
|
74
|
+
# decoding needed).
|
|
75
|
+
#
|
|
44
76
|
# @attribute [r] simple?
|
|
45
|
-
# @return [true, false]
|
|
77
|
+
# @return [true, false]
|
|
46
78
|
def simple? = @raw == @name
|
|
47
79
|
|
|
48
|
-
#
|
|
49
|
-
# This is composed by all {modifier} and the {key}.
|
|
50
|
-
#
|
|
51
|
-
# @example
|
|
52
|
-
# Terminal::KeyEvent["\e\r"].to_a
|
|
53
|
-
# => [:Alt, :Enter]
|
|
54
|
-
# Terminal::KeyEvent['a'].to_a
|
|
55
|
-
# => ['a']
|
|
56
|
-
# Terminal::KeyEvent["\e[<12;45;30m"].to_a
|
|
57
|
-
# => [:Shift, :Alt, :MBLeftUp]
|
|
80
|
+
# Modifier names followed by the key.
|
|
58
81
|
#
|
|
59
|
-
# @return [Array<Symbol, String>]
|
|
82
|
+
# @return [Array<Symbol, String>]
|
|
60
83
|
def to_a = @ary.dup
|
|
61
84
|
|
|
62
85
|
# @private
|
|
86
|
+
# Destructuring-friendly array representation.
|
|
87
|
+
#
|
|
88
|
+
# @return [Array<String>] +[raw]+ if {#simple?}
|
|
89
|
+
# @return [Array<String, String>] +[raw, name]+ otherwise
|
|
63
90
|
def to_ary = simple? ? [@raw] : [@raw, @name]
|
|
64
91
|
|
|
65
|
-
# @
|
|
92
|
+
# @return [String] the event {#name}
|
|
66
93
|
def to_s = @name.dup
|
|
67
94
|
|
|
68
95
|
# @private
|
|
@@ -73,48 +100,66 @@ module Terminal
|
|
|
73
100
|
@raw.freeze
|
|
74
101
|
@key.freeze
|
|
75
102
|
@position.freeze
|
|
103
|
+
@modifiers.freeze
|
|
76
104
|
@name.freeze
|
|
77
105
|
super
|
|
78
106
|
end
|
|
79
107
|
|
|
80
108
|
private
|
|
81
109
|
|
|
82
|
-
def initialize(raw, key,
|
|
110
|
+
def initialize(raw, key, modifier_bits, position)
|
|
83
111
|
@raw = raw
|
|
84
112
|
@key = key
|
|
85
|
-
@
|
|
113
|
+
@modifier_bits = modifier_bits
|
|
114
|
+
@modifiers =
|
|
115
|
+
MODIFIERS.filter_map { |b, n| n if modifier_bits.allbits?(b) }
|
|
86
116
|
@position = position
|
|
87
|
-
@ary =
|
|
117
|
+
@ary = @modifiers.dup
|
|
88
118
|
@ary << key if key
|
|
89
119
|
@name = @ary.join('+')
|
|
90
120
|
end
|
|
91
121
|
|
|
92
122
|
class << self
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
|
|
123
|
+
# Whether key event caching is enabled.
|
|
124
|
+
# When enabled, parsed events are frozen and reused for identical
|
|
125
|
+
# raw input sequences.
|
|
126
|
+
#
|
|
127
|
+
# @attribute [rw] caching
|
|
128
|
+
# @return [true, false]
|
|
129
|
+
def caching = @cache != nil
|
|
96
130
|
|
|
97
|
-
# @attribute [w] caching
|
|
98
131
|
def caching=(value)
|
|
99
132
|
value ? @cache ||= {} : @cache = nil
|
|
100
133
|
end
|
|
101
134
|
|
|
102
|
-
#
|
|
103
|
-
#
|
|
135
|
+
# All recognized key name symbols.
|
|
136
|
+
#
|
|
137
|
+
# @return [Array<Symbol>] sorted unique key names
|
|
104
138
|
def key_names
|
|
105
139
|
(
|
|
106
140
|
@names ||=
|
|
107
141
|
(@csiu.values + @csi.values + @ss3.values)
|
|
108
142
|
.uniq
|
|
109
|
-
.keep_if { Symbol ===
|
|
143
|
+
.keep_if { Symbol === it }
|
|
110
144
|
.sort!
|
|
111
145
|
).dup
|
|
112
146
|
end
|
|
113
147
|
|
|
114
|
-
#
|
|
148
|
+
# Parse a raw terminal input sequence into a KeyEvent.
|
|
149
|
+
#
|
|
150
|
+
# Supports single characters, ESC sequences, CSI-u protocol,
|
|
151
|
+
# legacy function keys, and mouse events (VT200 and SGR format).
|
|
115
152
|
#
|
|
116
|
-
# @
|
|
117
|
-
#
|
|
153
|
+
# @example Parse a single character
|
|
154
|
+
# Terminal::KeyEvent['a'].key # => "a"
|
|
155
|
+
# @example Parse escape sequence
|
|
156
|
+
# Terminal::KeyEvent["\e[A"].key # => :Up
|
|
157
|
+
# @example Parse mouse event
|
|
158
|
+
# Terminal::KeyEvent["\e[<0;10;5M"].key # => :MBLeft
|
|
159
|
+
#
|
|
160
|
+
# @param raw [String] the raw escape sequence or single character
|
|
161
|
+
# @return [KeyEvent] the parsed event (frozen; cached when caching
|
|
162
|
+
# is enabled)
|
|
118
163
|
def [](raw)
|
|
119
164
|
cached = @cache[raw] and return cached if @cache
|
|
120
165
|
return new(raw, *@single_key[raw.ord]) if raw.size == 1
|
|
@@ -127,8 +172,8 @@ module Terminal
|
|
|
127
172
|
return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
|
|
128
173
|
when '['
|
|
129
174
|
return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC [ ?
|
|
130
|
-
if raw.size == 6 && raw[2] == 'M'
|
|
131
|
-
return mouse_vt200(raw)
|
|
175
|
+
if raw.size == 6 && raw[2] == 'M'
|
|
176
|
+
return mouse_vt200(raw) # ESC [ M b c r
|
|
132
177
|
end
|
|
133
178
|
if raw.size > 4 && raw[2] == '1' && raw[3] == ';'
|
|
134
179
|
return csi(raw) # ESC [ 1 ; ...
|
|
@@ -148,7 +193,7 @@ module Terminal
|
|
|
148
193
|
end
|
|
149
194
|
|
|
150
195
|
# @private
|
|
151
|
-
def new(raw, key = raw,
|
|
196
|
+
def new(raw, key = raw, modifier_bits = 0, position = nil)
|
|
152
197
|
(@cache && position.nil? ? @cache[raw] ||= super : super).freeze
|
|
153
198
|
end
|
|
154
199
|
|
|
@@ -158,8 +203,8 @@ module Terminal
|
|
|
158
203
|
|
|
159
204
|
def esc1(raw, char)
|
|
160
205
|
# ESC ?
|
|
161
|
-
key,
|
|
162
|
-
new(raw, key || char,
|
|
206
|
+
key, modifier_bits = @esc1[char.ord]
|
|
207
|
+
new(raw, key || char, modifier_bits || 2)
|
|
163
208
|
end
|
|
164
209
|
|
|
165
210
|
def esc_esc(raw)
|
|
@@ -167,18 +212,18 @@ module Terminal
|
|
|
167
212
|
# ESC ESC ...
|
|
168
213
|
return esc1(raw, raw[2]) if raw.size == 3
|
|
169
214
|
ret = self[raw[1..]]
|
|
170
|
-
new(raw, ret.key, ret.
|
|
215
|
+
new(raw, ret.key, ret.modifier_bits | 2)
|
|
171
216
|
end
|
|
172
217
|
|
|
173
218
|
def csi(raw)
|
|
174
219
|
# ESC [ 1 ; [~ABCDEFHPQRS]
|
|
175
|
-
# ESC [ 1 ; <
|
|
220
|
+
# ESC [ 1 ; <modifier_bits> [~ABCDEFHPQRS]
|
|
176
221
|
return unknown(raw) if raw.size < 5
|
|
177
|
-
key,
|
|
178
|
-
|
|
179
|
-
return new(raw, nil,
|
|
180
|
-
|
|
181
|
-
new(raw, key,
|
|
222
|
+
key, modifier_bits = @ss3[raw[-1].ord]
|
|
223
|
+
modifier_bits ||= 0
|
|
224
|
+
return new(raw, nil, modifier_bits) unless key
|
|
225
|
+
modifier_bits |= [raw[4..-2].to_i - 1, 0].max if raw.size > 5
|
|
226
|
+
new(raw, key, modifier_bits)
|
|
182
227
|
end
|
|
183
228
|
|
|
184
229
|
def legacy(raw)
|
|
@@ -208,7 +253,7 @@ module Terminal
|
|
|
208
253
|
|
|
209
254
|
def mouse_vt200(raw)
|
|
210
255
|
# ESC [ M <btn> <col> <row>
|
|
211
|
-
mouse_event(raw, *raw[3..].chars.map! {
|
|
256
|
+
mouse_event(raw, *raw[3..].chars.map! { it.ord - 32 })
|
|
212
257
|
end
|
|
213
258
|
|
|
214
259
|
def mouse_sgr(raw)
|
|
@@ -228,25 +273,36 @@ module Terminal
|
|
|
228
273
|
return unknown(raw) if btn < 0 || col < 1 || row < 1
|
|
229
274
|
key = (btn & 1).nonzero? ? 1 : 0
|
|
230
275
|
key += 2 if btn.allbits?(2)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
276
|
+
modifier_bits = btn.allbits?(4) ? 1 : 0
|
|
277
|
+
modifier_bits += 2 if btn.allbits?(8)
|
|
278
|
+
modifier_bits += 4 if btn.allbits?(16)
|
|
279
|
+
modifier_bits += 256 if moved = btn.allbits?(32)
|
|
235
280
|
key += 4 if btn.allbits?(64)
|
|
236
281
|
key += 8 if btn.allbits?(128)
|
|
237
|
-
new(raw, mouse_name(key, up, moved),
|
|
282
|
+
new(raw, mouse_name(key, up, moved), modifier_bits, [row, col])
|
|
238
283
|
end
|
|
239
284
|
|
|
240
285
|
def mouse_name(key, up, moved)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
286
|
+
case key
|
|
287
|
+
when 0
|
|
288
|
+
up ? :MBLeftUp : :MBLeft
|
|
289
|
+
when 1
|
|
290
|
+
up ? :MBMiddleUp : :MBMiddle
|
|
291
|
+
when 2
|
|
292
|
+
up ? :MBRightUp : :MBRight
|
|
293
|
+
when 3
|
|
294
|
+
moved ? :Mouse : :MBUp
|
|
295
|
+
when 4
|
|
296
|
+
:MWUp
|
|
297
|
+
when 5
|
|
298
|
+
:MWDown
|
|
299
|
+
when 6
|
|
300
|
+
:MWLeft
|
|
301
|
+
when 7
|
|
302
|
+
:MWRight
|
|
303
|
+
else
|
|
304
|
+
:"MB_#{key - 8}#{'Up' if up}"
|
|
305
|
+
end
|
|
250
306
|
end
|
|
251
307
|
end
|
|
252
308
|
|
data/lib/terminal/input.rb
CHANGED
|
@@ -3,64 +3,73 @@
|
|
|
3
3
|
require 'io/console'
|
|
4
4
|
|
|
5
5
|
module Terminal
|
|
6
|
+
#
|
|
6
7
|
# @!group Attributes
|
|
8
|
+
#
|
|
7
9
|
|
|
8
10
|
# @attribute [r] self.input_mode
|
|
9
11
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# @
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# @
|
|
16
|
-
#
|
|
17
|
-
# @return [:dumb]
|
|
18
|
-
# for non-interactive input (pipes etc.)
|
|
19
|
-
# @return [:error]
|
|
20
|
-
# when input device is not avail (closed)
|
|
12
|
+
# Detect and return the current input mode.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# Terminal.input_mode # => :csi_u
|
|
16
|
+
#
|
|
17
|
+
# @attribute [r] input_mode
|
|
18
|
+
# @return [Symbol] +:csi_u+, +:legacy+, +:dumb+, or +:error+
|
|
21
19
|
|
|
20
|
+
#
|
|
22
21
|
# @!endgroup
|
|
23
|
-
|
|
22
|
+
#
|
|
24
23
|
# @!group Input methods
|
|
25
|
-
|
|
26
|
-
# @!method self.read_key_event
|
|
27
|
-
# Read next {KeyEvent} from standard input.
|
|
28
24
|
#
|
|
29
|
-
# @return [KeyEvent] next event
|
|
30
|
-
# @return [nil] in error case
|
|
31
25
|
|
|
32
26
|
# @!method self.on_key_event(mouse: false, mouse_move: false, focus: false, &block)
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# @
|
|
48
|
-
#
|
|
49
|
-
# @
|
|
50
|
-
#
|
|
27
|
+
#
|
|
28
|
+
# Enter a key event loop, yielding each event to the block.
|
|
29
|
+
#
|
|
30
|
+
# Enables optional mouse and focus event reporting. The block
|
|
31
|
+
# receives {KeyEvent} instances until input ends or the block
|
|
32
|
+
# breaks.
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# Terminal.on_key_event(mouse: true) do |event|
|
|
36
|
+
# break if event.name == 'ESC'
|
|
37
|
+
# puts event.name
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# @param mouse [true, false] enable mouse button events
|
|
41
|
+
# @param mouse_move [true, false] enable mouse move events
|
|
42
|
+
# @param focus [true, false] enable terminal focus/unfocus events
|
|
43
|
+
# @yield [event] called for each input event
|
|
44
|
+
# @yieldparam event [KeyEvent] the parsed key event
|
|
45
|
+
# @return [nil, false] +false+ on I/O error
|
|
46
|
+
# @raise [RuntimeError] if already inside a key event loop
|
|
47
|
+
|
|
48
|
+
# @!method self.read_key_event
|
|
49
|
+
#
|
|
50
|
+
# Read a single key event (blocking).
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# event = Terminal.read_key_event
|
|
54
|
+
# puts "You pressed: #{event.name}"
|
|
55
|
+
#
|
|
56
|
+
# @return [KeyEvent, false] the next key event, or +false+ on error
|
|
51
57
|
|
|
58
|
+
#
|
|
52
59
|
# @!endgroup
|
|
60
|
+
#
|
|
53
61
|
|
|
62
|
+
# @private
|
|
54
63
|
module Input
|
|
55
64
|
def input_mode
|
|
56
65
|
@in_recursion = 0
|
|
57
66
|
case @input_mode = __find_input_mode
|
|
58
67
|
when :legacy, :csi_u
|
|
59
68
|
@in = IO.console
|
|
60
|
-
require_relative
|
|
69
|
+
require_relative('input/ansi')
|
|
61
70
|
extend AnsiInput
|
|
62
71
|
when :dumb
|
|
63
|
-
require_relative
|
|
72
|
+
require_relative('input/dumb')
|
|
64
73
|
extend DumpInput
|
|
65
74
|
else
|
|
66
75
|
__input_error
|
|
@@ -68,12 +77,12 @@ module Terminal
|
|
|
68
77
|
@input_mode
|
|
69
78
|
end
|
|
70
79
|
|
|
71
|
-
def on_key_event(mouse: false, mouse_move: false, focus: false, &
|
|
80
|
+
def on_key_event(mouse: false, mouse_move: false, focus: false, &)
|
|
72
81
|
input_mode
|
|
73
|
-
on_key_event(mouse
|
|
82
|
+
on_key_event(mouse:, mouse_move:, focus:, &)
|
|
74
83
|
end
|
|
75
84
|
|
|
76
|
-
def read_key_event = on_key_event { return
|
|
85
|
+
def read_key_event = on_key_event { return it }
|
|
77
86
|
|
|
78
87
|
private
|
|
79
88
|
|
|
@@ -94,10 +103,12 @@ module Terminal
|
|
|
94
103
|
return :legacy if im == 'legacy'
|
|
95
104
|
return :dumb if im == 'dumb' || !STDIN.tty?
|
|
96
105
|
con = IO.console
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
STDIN.noecho do
|
|
107
|
+
return :legacy if con.syswrite("\e[>1u\e[?u\e[c") != 12
|
|
108
|
+
inp = +''
|
|
109
|
+
inp << con.getch until inp.rindex('c')
|
|
110
|
+
return :legacy unless inp.include?("\e[?1u")
|
|
111
|
+
end
|
|
101
112
|
at_exit do
|
|
102
113
|
IO.console.syswrite("\e[<u")
|
|
103
114
|
rescue StandardError
|
data/lib/terminal/output/ansi.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Terminal
|
|
4
|
+
# @private
|
|
4
5
|
module AnsiOutput
|
|
5
6
|
def output_mode = :ansi
|
|
6
7
|
def ansi? = true
|
|
@@ -40,8 +41,8 @@ module Terminal
|
|
|
40
41
|
@con = nil
|
|
41
42
|
end
|
|
42
43
|
|
|
43
|
-
def raw_write(
|
|
44
|
-
@out.syswrite(
|
|
44
|
+
def raw_write(object)
|
|
45
|
+
@out.syswrite(object)
|
|
45
46
|
rescue IOError, SystemCallError
|
|
46
47
|
__output_error(nil)
|
|
47
48
|
end
|
|
@@ -56,15 +57,48 @@ module Terminal
|
|
|
56
57
|
def print(*objects, bbcode: true)
|
|
57
58
|
return if objects.empty?
|
|
58
59
|
return @out.print(*objects) unless bbcode
|
|
59
|
-
@out.print(*objects.map! { Ansi.bbcode(
|
|
60
|
+
@out.print(*objects.map! { Ansi.bbcode(it) })
|
|
60
61
|
rescue IOError, SystemCallError
|
|
61
62
|
__output_error(nil)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
65
|
def puts(*objects, bbcode: true)
|
|
65
|
-
return @out.puts(objects.empty? ? nil : objects) unless bbcode
|
|
66
66
|
objects.flatten!
|
|
67
|
-
@out.puts
|
|
67
|
+
return @out.puts if objects.empty?
|
|
68
|
+
return @out.puts(objects) unless bbcode
|
|
69
|
+
@out.puts(objects.map! { Ansi.bbcode(it) })
|
|
70
|
+
rescue IOError, SystemCallError
|
|
71
|
+
__output_error(nil)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fputs(
|
|
75
|
+
*objects,
|
|
76
|
+
bbcode: true,
|
|
77
|
+
spaces: true,
|
|
78
|
+
eol: true,
|
|
79
|
+
align: nil,
|
|
80
|
+
width: columns,
|
|
81
|
+
height: nil,
|
|
82
|
+
padding: nil,
|
|
83
|
+
prefix: nil,
|
|
84
|
+
suffix: nil
|
|
85
|
+
)
|
|
86
|
+
objects.flatten!
|
|
87
|
+
@out.puts(
|
|
88
|
+
Text::Formatter.format(
|
|
89
|
+
*objects,
|
|
90
|
+
ansi: true,
|
|
91
|
+
bbcode:,
|
|
92
|
+
spaces:,
|
|
93
|
+
eol:,
|
|
94
|
+
align:,
|
|
95
|
+
width:,
|
|
96
|
+
height:,
|
|
97
|
+
padding:,
|
|
98
|
+
prefix:,
|
|
99
|
+
suffix:
|
|
100
|
+
)
|
|
101
|
+
)
|
|
68
102
|
rescue IOError, SystemCallError
|
|
69
103
|
__output_error(nil)
|
|
70
104
|
end
|
|
@@ -101,7 +135,11 @@ module Terminal
|
|
|
101
135
|
def __init_tty
|
|
102
136
|
require('io/console')
|
|
103
137
|
@con = IO.console
|
|
104
|
-
|
|
138
|
+
return unless Signal.list.key?('WINCH')
|
|
139
|
+
Signal.trap('WINCH') do
|
|
140
|
+
@size = nil
|
|
141
|
+
@on_resize&.call
|
|
142
|
+
end
|
|
105
143
|
end
|
|
106
144
|
|
|
107
145
|
private_class_method def self.extended(mod)
|