tty2-reader 0.9.0.1
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 +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/lib/tty2/reader/completer.rb +188 -0
- data/lib/tty2/reader/completion_event.rb +36 -0
- data/lib/tty2/reader/completions.rb +107 -0
- data/lib/tty2/reader/console.rb +68 -0
- data/lib/tty2/reader/history.rb +184 -0
- data/lib/tty2/reader/key_event.rb +58 -0
- data/lib/tty2/reader/keys.rb +166 -0
- data/lib/tty2/reader/line.rb +367 -0
- data/lib/tty2/reader/mode.rb +42 -0
- data/lib/tty2/reader/version.rb +7 -0
- data/lib/tty2/reader/win_api.rb +51 -0
- data/lib/tty2/reader/win_console.rb +90 -0
- data/lib/tty2/reader.rb +632 -0
- data/lib/tty2-reader.rb +1 -0
- metadata +143 -0
data/lib/tty2/reader.rb
ADDED
@@ -0,0 +1,632 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-cursor"
|
4
|
+
require "tty-screen"
|
5
|
+
require "wisper"
|
6
|
+
|
7
|
+
require_relative "reader/completer"
|
8
|
+
require_relative "reader/completion_event"
|
9
|
+
require_relative "reader/history"
|
10
|
+
require_relative "reader/line"
|
11
|
+
require_relative "reader/key_event"
|
12
|
+
require_relative "reader/console"
|
13
|
+
require_relative "reader/win_console"
|
14
|
+
require_relative "reader/version"
|
15
|
+
|
16
|
+
module TTY2
|
17
|
+
# A class responsible for reading character input from STDIN
|
18
|
+
#
|
19
|
+
# Used internally to provide key and line reading functionality
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
class Reader
|
23
|
+
include Wisper::Publisher
|
24
|
+
|
25
|
+
# Key codes
|
26
|
+
BACKSPACE = 8
|
27
|
+
TAB = 9
|
28
|
+
NEWLINE = 10
|
29
|
+
CARRIAGE_RETURN = 13
|
30
|
+
DELETE = 127
|
31
|
+
|
32
|
+
# Keys that terminate input
|
33
|
+
EXIT_KEYS = %i[ctrl_d ctrl_z].freeze
|
34
|
+
|
35
|
+
# Pattern to check if line ends with a line break character
|
36
|
+
END_WITH_LINE_BREAK = /(\r|\n)$/.freeze
|
37
|
+
|
38
|
+
# Raised when the user hits the interrupt key(Control-C)
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
InputInterrupt = Class.new(Interrupt)
|
42
|
+
|
43
|
+
# Check if Windowz mode
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def self.windows?
|
49
|
+
::File::ALT_SEPARATOR == "\\"
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :input
|
53
|
+
|
54
|
+
attr_reader :output
|
55
|
+
|
56
|
+
attr_reader :env
|
57
|
+
|
58
|
+
attr_reader :track_history
|
59
|
+
alias track_history? track_history
|
60
|
+
|
61
|
+
# The handler for finding word completion suggestions
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
attr_reader :completion_handler
|
65
|
+
|
66
|
+
# The suffix to add to suggested word completion
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
attr_reader :completion_suffix
|
70
|
+
|
71
|
+
attr_reader :completion_cycling
|
72
|
+
|
73
|
+
attr_reader :console
|
74
|
+
|
75
|
+
attr_reader :cursor
|
76
|
+
|
77
|
+
# Initialize a Reader
|
78
|
+
#
|
79
|
+
# @param [IO] input
|
80
|
+
# the input stream
|
81
|
+
# @param [IO] output
|
82
|
+
# the output stream
|
83
|
+
# @param [Symbol] interrupt
|
84
|
+
# the way to handle the Ctrl+C key out of :signal, :exit, :noop
|
85
|
+
# @param [Hash] env
|
86
|
+
# the environment variables
|
87
|
+
# @param [Boolean] track_history
|
88
|
+
# disable line history tracking, true by default
|
89
|
+
# @param [Boolean] history_cycle
|
90
|
+
# allow cycling through history, false by default
|
91
|
+
# @param [Boolean] history_duplicates
|
92
|
+
# allow duplicate entires, false by default
|
93
|
+
# @param [Proc] history_exclude
|
94
|
+
# exclude lines from history, by default all lines are stored
|
95
|
+
# @param [Proc] completion_handler
|
96
|
+
# the hanlder for finding word completion suggestions
|
97
|
+
# @param [String] completion_suffix
|
98
|
+
# the suffix to add to suggested word completion
|
99
|
+
# @param [Boolean] completion_cycling
|
100
|
+
# enable cycling through completions, true by default
|
101
|
+
#
|
102
|
+
# @api public
|
103
|
+
def initialize(input: $stdin, output: $stdout, interrupt: :error,
|
104
|
+
env: ENV, track_history: true, history_cycle: false,
|
105
|
+
history_exclude: History::DEFAULT_EXCLUDE,
|
106
|
+
history_size: History::DEFAULT_SIZE,
|
107
|
+
history_duplicates: false,
|
108
|
+
completion_handler: nil, completion_suffix: "",
|
109
|
+
completion_cycling: true)
|
110
|
+
@input = input
|
111
|
+
@output = output
|
112
|
+
@interrupt = interrupt
|
113
|
+
@env = env
|
114
|
+
@track_history = track_history
|
115
|
+
@history_cycle = history_cycle
|
116
|
+
@history_exclude = history_exclude
|
117
|
+
@history_duplicates = history_duplicates
|
118
|
+
@history_size = history_size
|
119
|
+
@completion_handler = completion_handler
|
120
|
+
@completion_suffix = completion_suffix
|
121
|
+
@completion_cycling = completion_cycling
|
122
|
+
@completer = Completer.new(handler: completion_handler,
|
123
|
+
suffix: completion_suffix,
|
124
|
+
cycling: completion_cycling)
|
125
|
+
@console = select_console(input)
|
126
|
+
@history = History.new(history_size) do |h|
|
127
|
+
h.cycle = history_cycle
|
128
|
+
h.duplicates = history_duplicates
|
129
|
+
h.exclude = history_exclude
|
130
|
+
end
|
131
|
+
@cursor = TTY::Cursor
|
132
|
+
end
|
133
|
+
|
134
|
+
# Set completion handler
|
135
|
+
#
|
136
|
+
# @param [Proc] handler
|
137
|
+
# the handler for finding word completion suggestions
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def completion_handler=(handler)
|
141
|
+
@completion_handler = handler
|
142
|
+
@completer.handler = handler
|
143
|
+
end
|
144
|
+
|
145
|
+
# Set completion suffix
|
146
|
+
#
|
147
|
+
# @param [String] suffix
|
148
|
+
# the suffix to add to suggested word completion
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def completion_suffix=(suffix)
|
152
|
+
@completion_suffix = suffix
|
153
|
+
@completer.suffix = suffix
|
154
|
+
end
|
155
|
+
|
156
|
+
# Set completion cycling
|
157
|
+
#
|
158
|
+
# @param [Boolean] cycling
|
159
|
+
# whether to cycle through completion suggestionsor not, defaults to true
|
160
|
+
#
|
161
|
+
# @api public
|
162
|
+
def completion_cycling=(cycling)
|
163
|
+
@completion_cycling = cycling
|
164
|
+
@completer.cycling = cycling
|
165
|
+
end
|
166
|
+
|
167
|
+
alias old_subcribe subscribe
|
168
|
+
|
169
|
+
# Subscribe to receive key events
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# reader.subscribe(MyListener.new)
|
173
|
+
#
|
174
|
+
# @return [self|yield]
|
175
|
+
#
|
176
|
+
# @api public
|
177
|
+
def subscribe(listener, options = {})
|
178
|
+
old_subcribe(listener, options)
|
179
|
+
object = self
|
180
|
+
if block_given?
|
181
|
+
object = yield
|
182
|
+
unsubscribe(listener)
|
183
|
+
end
|
184
|
+
object
|
185
|
+
end
|
186
|
+
|
187
|
+
# Unsubscribe from receiving key events
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# reader.unsubscribe(my_listener)
|
191
|
+
#
|
192
|
+
# @return [void]
|
193
|
+
#
|
194
|
+
# @api public
|
195
|
+
def unsubscribe(listener)
|
196
|
+
registry = send(:local_registrations)
|
197
|
+
registry.each do |object|
|
198
|
+
if object.listener.equal?(listener)
|
199
|
+
registry.delete(object)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Select appropriate console
|
205
|
+
#
|
206
|
+
# @api private
|
207
|
+
def select_console(input)
|
208
|
+
if self.class.windows? && !env["TTY_TEST"]
|
209
|
+
WinConsole.new(input)
|
210
|
+
else
|
211
|
+
Console.new(input)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Get input in unbuffered mode.
|
216
|
+
#
|
217
|
+
# @example
|
218
|
+
# unbufferred do
|
219
|
+
# ...
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# @api public
|
223
|
+
def unbufferred(&block)
|
224
|
+
bufferring = output.sync
|
225
|
+
# Immediately flush output
|
226
|
+
output.sync = true
|
227
|
+
block[] if block_given?
|
228
|
+
ensure
|
229
|
+
output.sync = bufferring
|
230
|
+
end
|
231
|
+
|
232
|
+
# Read a keypress including invisible multibyte codes and return
|
233
|
+
# a character as a string.
|
234
|
+
# Nothing is echoed to the console. This call will block for a
|
235
|
+
# single keypress, but will not wait for Enter to be pressed.
|
236
|
+
#
|
237
|
+
# @param [Boolean] echo
|
238
|
+
# whether to echo chars back or not, defaults to false
|
239
|
+
# @option [Boolean] raw
|
240
|
+
# whenther raw mode is enabled, defaults to true
|
241
|
+
# @option [Boolean] nonblock
|
242
|
+
# whether to wait for input or not, defaults to false
|
243
|
+
#
|
244
|
+
# @return [String]
|
245
|
+
#
|
246
|
+
# @api public
|
247
|
+
def read_keypress(echo: false, raw: true, nonblock: false)
|
248
|
+
codes = unbufferred do
|
249
|
+
get_codes(echo: echo, raw: raw, nonblock: nonblock)
|
250
|
+
end
|
251
|
+
char = codes ? codes.pack("U*") : nil
|
252
|
+
|
253
|
+
trigger_key_event(char) if char
|
254
|
+
char
|
255
|
+
end
|
256
|
+
alias read_char read_keypress
|
257
|
+
|
258
|
+
# Get input code points
|
259
|
+
#
|
260
|
+
# @param [Boolean] echo
|
261
|
+
# whether to echo chars back or not, defaults to false
|
262
|
+
# @option [Boolean] raw
|
263
|
+
# whenther raw mode is enabled, defaults to true
|
264
|
+
# @option [Boolean] nonblock
|
265
|
+
# whether to wait for input or not, defaults to false
|
266
|
+
# @param [Array[Integer]] codes
|
267
|
+
# the currently read char code points
|
268
|
+
#
|
269
|
+
# @return [Array[Integer]]
|
270
|
+
#
|
271
|
+
# @api private
|
272
|
+
def get_codes(echo: true, raw: false, nonblock: false, codes: [])
|
273
|
+
char = console.get_char(echo: echo, raw: raw, nonblock: nonblock)
|
274
|
+
handle_interrupt if console.keys[char] == :ctrl_c
|
275
|
+
return if char.nil?
|
276
|
+
|
277
|
+
codes << char.ord
|
278
|
+
condition = proc { |escape|
|
279
|
+
(codes - escape).empty? ||
|
280
|
+
(escape - codes).empty? &&
|
281
|
+
!(64..126).cover?(codes.last)
|
282
|
+
}
|
283
|
+
|
284
|
+
while console.escape_codes.any?(&condition)
|
285
|
+
char_codes = get_codes(echo: echo, raw: raw,
|
286
|
+
nonblock: true, codes: codes)
|
287
|
+
break if char_codes.nil?
|
288
|
+
end
|
289
|
+
|
290
|
+
codes
|
291
|
+
end
|
292
|
+
|
293
|
+
# Get a single line from STDIN. Each key pressed is echoed
|
294
|
+
# back to the shell. The input terminates when enter or
|
295
|
+
# return key is pressed.
|
296
|
+
#
|
297
|
+
# @param [String] prompt
|
298
|
+
# the prompt to display before input
|
299
|
+
# @param [String] value
|
300
|
+
# the value to pre-populate line with
|
301
|
+
# @param [Boolean] echo
|
302
|
+
# whether to echo chars back or not, defaults to false
|
303
|
+
# @param [Array<Symbol>] exit_keys
|
304
|
+
# the custom keys to exit line editing
|
305
|
+
# @option [Boolean] raw
|
306
|
+
# whenther raw mode is enabled, defaults to true
|
307
|
+
# @option [Boolean] nonblock
|
308
|
+
# whether to wait for input or not, defaults to false
|
309
|
+
#
|
310
|
+
# @return [String]
|
311
|
+
#
|
312
|
+
# @api public
|
313
|
+
def read_line(prompt = "", value: "", echo: true, raw: true,
|
314
|
+
nonblock: false, exit_keys: nil)
|
315
|
+
line = Line.new(value, prompt: prompt)
|
316
|
+
screen_width = TTY::Screen.width
|
317
|
+
history_in_use = false
|
318
|
+
previous_key_name = ""
|
319
|
+
buffer = ""
|
320
|
+
|
321
|
+
output.print(line)
|
322
|
+
|
323
|
+
while (codes = get_codes(echo: echo, raw: raw, nonblock: nonblock)) &&
|
324
|
+
(code = codes[0])
|
325
|
+
char = codes.pack("U*")
|
326
|
+
key_name = console.keys[char]
|
327
|
+
|
328
|
+
if exit_keys && exit_keys.include?(key_name)
|
329
|
+
trigger_key_event(char, line: line)
|
330
|
+
break
|
331
|
+
end
|
332
|
+
|
333
|
+
if raw && echo
|
334
|
+
clear_display(line, screen_width)
|
335
|
+
end
|
336
|
+
|
337
|
+
if (key_name == :tab || code == TAB || key_name == :shift_tab) &&
|
338
|
+
completion_handler
|
339
|
+
initial = previous_key_name != :tab && previous_key_name != :shift_tab
|
340
|
+
direction = key_name == :shift_tab ? :previous : :next
|
341
|
+
if completion = @completer.complete(line, initial: initial,
|
342
|
+
direction: direction)
|
343
|
+
trigger_completion_event(completion, line.to_s)
|
344
|
+
end
|
345
|
+
elsif key_name == :escape && completion_handler &&
|
346
|
+
(previous_key_name == :tab || previous_key_name == :shift_tab)
|
347
|
+
@completer.cancel(line)
|
348
|
+
elsif key_name == :backspace || code == BACKSPACE
|
349
|
+
if !line.start?
|
350
|
+
line.left
|
351
|
+
line.delete
|
352
|
+
end
|
353
|
+
elsif key_name == :delete || code == DELETE
|
354
|
+
line.delete
|
355
|
+
elsif key_name.to_s =~ /ctrl_/
|
356
|
+
# skip
|
357
|
+
elsif key_name == :up
|
358
|
+
@history.replace(line.text) if history_in_use
|
359
|
+
if history_previous?
|
360
|
+
line.replace(history_previous(skip: !history_in_use))
|
361
|
+
history_in_use = true
|
362
|
+
end
|
363
|
+
elsif key_name == :down
|
364
|
+
@history.replace(line.text) if history_in_use
|
365
|
+
if history_next?
|
366
|
+
line.replace(history_next)
|
367
|
+
elsif history_in_use
|
368
|
+
line.replace(buffer)
|
369
|
+
history_in_use = false
|
370
|
+
end
|
371
|
+
elsif key_name == :left
|
372
|
+
line.left
|
373
|
+
elsif key_name == :right
|
374
|
+
line.right
|
375
|
+
elsif key_name == :home
|
376
|
+
line.move_to_start
|
377
|
+
elsif key_name == :end
|
378
|
+
line.move_to_end
|
379
|
+
else
|
380
|
+
if raw && [CARRIAGE_RETURN, NEWLINE].include?(code)
|
381
|
+
char = "\n"
|
382
|
+
line.move_to_end
|
383
|
+
end
|
384
|
+
line.insert(char)
|
385
|
+
buffer = line.text unless history_in_use
|
386
|
+
end
|
387
|
+
|
388
|
+
if (key_name == :backspace || code == BACKSPACE) && echo
|
389
|
+
if raw
|
390
|
+
output.print("\e[1X") unless line.start?
|
391
|
+
else
|
392
|
+
output.print(?\s + (line.start? ? "" : ?\b))
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
previous_key_name = key_name
|
397
|
+
|
398
|
+
# trigger before line is printed to allow for line changes
|
399
|
+
trigger_key_event(char, line: line)
|
400
|
+
|
401
|
+
if raw && echo
|
402
|
+
output.print(line.to_s)
|
403
|
+
if char == "\n"
|
404
|
+
line.move_to_start
|
405
|
+
elsif !line.end? # readjust cursor position
|
406
|
+
output.print(cursor.backward(line.text_size - line.cursor))
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
if [CARRIAGE_RETURN, NEWLINE].include?(code)
|
411
|
+
buffer = ""
|
412
|
+
output.puts unless echo
|
413
|
+
break
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
if track_history? && echo
|
418
|
+
add_to_history(line.text.rstrip)
|
419
|
+
end
|
420
|
+
|
421
|
+
line.text
|
422
|
+
end
|
423
|
+
|
424
|
+
# Clear display for the current line input
|
425
|
+
#
|
426
|
+
# Handles clearing input that is longer than the current
|
427
|
+
# terminal width which allows copy & pasting long strings.
|
428
|
+
#
|
429
|
+
# @param [Line] line
|
430
|
+
# the line to display
|
431
|
+
# @param [Number] screen_width
|
432
|
+
# the terminal screen width
|
433
|
+
#
|
434
|
+
# @api private
|
435
|
+
def clear_display(line, screen_width)
|
436
|
+
total_lines = count_screen_lines(line.size, screen_width)
|
437
|
+
current_line = count_screen_lines(line.prompt_size + line.cursor, screen_width)
|
438
|
+
lines_down = total_lines - current_line
|
439
|
+
|
440
|
+
output.print(cursor.down(lines_down)) unless lines_down.zero?
|
441
|
+
output.print(cursor.clear_lines(total_lines))
|
442
|
+
end
|
443
|
+
|
444
|
+
# Count the number of screen lines given line takes up in terminal
|
445
|
+
#
|
446
|
+
# @param [Integer] line_or_size
|
447
|
+
# the current line or its length
|
448
|
+
# @param [Integer] screen_width
|
449
|
+
# the width of terminal screen
|
450
|
+
#
|
451
|
+
# @return [Integer]
|
452
|
+
#
|
453
|
+
# @api public
|
454
|
+
def count_screen_lines(line_or_size, screen_width = TTY::Screen.width)
|
455
|
+
line_size = if line_or_size.is_a?(Integer)
|
456
|
+
line_or_size
|
457
|
+
else
|
458
|
+
Line.sanitize(line_or_size).size
|
459
|
+
end
|
460
|
+
# new character + we don't want to add new line on screen_width
|
461
|
+
new_chars = self.class.windows? ? -1 : 1
|
462
|
+
1 + [0, (line_size - new_chars) / screen_width].max
|
463
|
+
end
|
464
|
+
|
465
|
+
# Read multiple lines and return them in an array.
|
466
|
+
# Skip empty lines in the returned lines array.
|
467
|
+
# The input gathering is terminated by Ctrl+d or Ctrl+z.
|
468
|
+
#
|
469
|
+
# @param [String] prompt
|
470
|
+
# the prompt displayed before the input
|
471
|
+
# @param [String] value
|
472
|
+
# the value to pre-populate line with
|
473
|
+
# @param [Boolean] echo
|
474
|
+
# whether to echo chars back or not, defaults to false
|
475
|
+
# @param [Array<Symbol>] exit_keys
|
476
|
+
# the custom keys to exit line editing
|
477
|
+
# @option [Boolean] raw
|
478
|
+
# whenther raw mode is enabled, defaults to true
|
479
|
+
# @option [Boolean] nonblock
|
480
|
+
# whether to wait for input or not, defaults to false
|
481
|
+
#
|
482
|
+
# @yield [String] line
|
483
|
+
#
|
484
|
+
# @return [Array[String]]
|
485
|
+
#
|
486
|
+
# @api public
|
487
|
+
def read_multiline(prompt = "", value: "", echo: true, raw: true,
|
488
|
+
nonblock: false, exit_keys: EXIT_KEYS)
|
489
|
+
lines = []
|
490
|
+
stop = false
|
491
|
+
clear_value = !value.to_s.empty?
|
492
|
+
|
493
|
+
loop do
|
494
|
+
line = read_line(prompt, value: value, echo: echo, raw: raw,
|
495
|
+
nonblock: nonblock, exit_keys: exit_keys).to_s
|
496
|
+
if clear_value
|
497
|
+
clear_value = false
|
498
|
+
value = ""
|
499
|
+
end
|
500
|
+
break if line.empty?
|
501
|
+
|
502
|
+
stop = line.match(END_WITH_LINE_BREAK).nil?
|
503
|
+
next if line !~ /\S/ && !stop
|
504
|
+
|
505
|
+
if block_given?
|
506
|
+
yield(line)
|
507
|
+
else
|
508
|
+
lines << line
|
509
|
+
end
|
510
|
+
break if stop
|
511
|
+
end
|
512
|
+
|
513
|
+
lines
|
514
|
+
end
|
515
|
+
alias read_lines read_multiline
|
516
|
+
|
517
|
+
# Expose event broadcasting
|
518
|
+
#
|
519
|
+
# @api public
|
520
|
+
def trigger(event, *args)
|
521
|
+
publish(event, *args)
|
522
|
+
end
|
523
|
+
|
524
|
+
# Add a line to history
|
525
|
+
#
|
526
|
+
# @param [String] line
|
527
|
+
#
|
528
|
+
# @api private
|
529
|
+
def add_to_history(line)
|
530
|
+
@history.push(line)
|
531
|
+
end
|
532
|
+
|
533
|
+
# Check if history has next line
|
534
|
+
#
|
535
|
+
# @param [Boolean]
|
536
|
+
#
|
537
|
+
# @api private
|
538
|
+
def history_next?
|
539
|
+
@history.next?
|
540
|
+
end
|
541
|
+
|
542
|
+
# Move history to the next line
|
543
|
+
#
|
544
|
+
# @return [String]
|
545
|
+
# the next line
|
546
|
+
#
|
547
|
+
# @api private
|
548
|
+
def history_next
|
549
|
+
@history.next
|
550
|
+
@history.get
|
551
|
+
end
|
552
|
+
|
553
|
+
# Check if history has previous line
|
554
|
+
#
|
555
|
+
# @return [Boolean]
|
556
|
+
#
|
557
|
+
# @api private
|
558
|
+
def history_previous?
|
559
|
+
@history.previous?
|
560
|
+
end
|
561
|
+
|
562
|
+
# Move history to the previous line
|
563
|
+
#
|
564
|
+
# @param [Boolean] skip
|
565
|
+
# whether or not to move history index
|
566
|
+
#
|
567
|
+
# @return [String]
|
568
|
+
# the previous line
|
569
|
+
#
|
570
|
+
# @api private
|
571
|
+
def history_previous(skip: false)
|
572
|
+
@history.previous unless skip
|
573
|
+
@history.get
|
574
|
+
end
|
575
|
+
|
576
|
+
# Inspect class name and public attributes
|
577
|
+
#
|
578
|
+
# @return [String]
|
579
|
+
#
|
580
|
+
# @api public
|
581
|
+
def inspect
|
582
|
+
"#<#{self.class}: @input=#{input}, @output=#{output}>"
|
583
|
+
end
|
584
|
+
|
585
|
+
private
|
586
|
+
|
587
|
+
# Trigger completion event
|
588
|
+
#
|
589
|
+
# @param [String] completion
|
590
|
+
# the suggested word completion
|
591
|
+
# @param [Line] line
|
592
|
+
# the line with word to complete
|
593
|
+
#
|
594
|
+
# @api private
|
595
|
+
def trigger_completion_event(completion, line)
|
596
|
+
completion_event = CompletionEvent.new(@completer, completion, line)
|
597
|
+
trigger(:complete, completion_event)
|
598
|
+
end
|
599
|
+
|
600
|
+
# Publish event
|
601
|
+
#
|
602
|
+
# @param [String] char
|
603
|
+
# the key pressed
|
604
|
+
#
|
605
|
+
# @return [nil]
|
606
|
+
#
|
607
|
+
# @api private
|
608
|
+
def trigger_key_event(char, line: Line.new)
|
609
|
+
event = KeyEvent.from(console.keys, char, line)
|
610
|
+
trigger(:"key#{event.key.name}", event) if event.trigger?
|
611
|
+
trigger(:keypress, event)
|
612
|
+
end
|
613
|
+
|
614
|
+
# Handle input interrupt based on provided value
|
615
|
+
#
|
616
|
+
# @api private
|
617
|
+
def handle_interrupt
|
618
|
+
case @interrupt
|
619
|
+
when :signal
|
620
|
+
Process.kill("SIGINT", Process.pid)
|
621
|
+
when :exit
|
622
|
+
exit(130)
|
623
|
+
when Proc
|
624
|
+
@interrupt.call
|
625
|
+
when :noop
|
626
|
+
# Noop
|
627
|
+
else
|
628
|
+
raise InputInterrupt
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end # Reader
|
632
|
+
end # TTY2
|
data/lib/tty2-reader.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'tty2/reader'
|