tty-reader 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +52 -11
- data/benchmarks/speed_read_char.rb +34 -0
- data/benchmarks/speed_read_line.rb +34 -0
- data/examples/shell.rb +2 -2
- data/lib/tty-reader.rb +0 -2
- data/lib/tty/reader.rb +12 -7
- data/lib/tty/reader/console.rb +9 -2
- data/lib/tty/reader/history.rb +0 -1
- data/lib/tty/reader/key_event.rb +3 -4
- data/lib/tty/reader/keys.rb +0 -1
- data/lib/tty/reader/line.rb +12 -5
- data/lib/tty/reader/mode.rb +0 -1
- data/lib/tty/reader/version.rb +2 -2
- data/lib/tty/reader/win_api.rb +1 -4
- data/lib/tty/reader/win_console.rb +0 -1
- data/spec/spec_helper.rb +43 -0
- data/spec/unit/history_spec.rb +177 -0
- data/spec/unit/key_event_spec.rb +102 -0
- data/spec/unit/line_spec.rb +159 -0
- data/spec/unit/publish_keypress_event_spec.rb +109 -0
- data/spec/unit/read_keypress_spec.rb +96 -0
- data/spec/unit/read_line_spec.rb +69 -0
- data/spec/unit/read_multiline_spec.rb +76 -0
- data/spec/unit/subscribe_spec.rb +74 -0
- data/tty-reader.gemspec +4 -3
- metadata +17 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 995b414e2605c556220168d30b3f894771e021f2956aa75821fb3504f5ae6589
|
4
|
+
data.tar.gz: 4bfa84cd59cab3b14410d4eac314ae819765d94230c1c9b2a1ce94c09e13c80d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 061eeb4dc0698c1ff226ad3a2bc9fe69e4c519fe455642bda6bb9675e758e52c385335bb1604256f2002c75f6b2ba8528007e8df9d332815df8f072b75bd1388
|
7
|
+
data.tar.gz: e964e5cf12b271ce1fee675afc1d348f7f6c59a01f007580936c2544e672f784a1297e0f7f8ee6acce2d6ee4f867ca32b2142d3d376f5a4a28c82e2ab6521713
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## [v0.5.0] - 2018-11-24
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Add KeyEvent#line to expose current line in key event callbacks
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
* Fix Esc key by differentiating between escaped keys and actual escape input
|
10
|
+
* Fix line editing to correctly insert next to last character
|
11
|
+
|
12
|
+
## [v0.4.0] - 2018-08-05
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
* Change to update tty-screen & tty-cursor dependencies
|
16
|
+
|
17
|
+
## [v0.3.0] - 2018-04-29
|
18
|
+
|
19
|
+
### Added
|
20
|
+
* Add Reader#unsubscribe to allow stop listening to local key events
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
* Change Reader#subscribe to allow to listening for key events only inside a block
|
24
|
+
* Change to group xterm keys for navigation
|
25
|
+
|
26
|
+
## [v0.2.0] - 2018-01-01
|
27
|
+
|
28
|
+
### Added
|
29
|
+
* Add home & end keys support in #read_line
|
30
|
+
* Add tty-screen & tty-cursor dependencies
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
* Change Codes to Keys and inverse keys lookup to allow for different system keys matching same name.
|
34
|
+
* Change Reader#initialize to only accept options and make input and output options as well.
|
35
|
+
* Change #read_line to print newline character in noecho mode
|
36
|
+
* Change Reader::Line to include prompt prefix
|
37
|
+
* Change Reader#initialize to only accept options in place of positional arguments
|
38
|
+
* Change Reader to expose history options
|
39
|
+
|
40
|
+
### Fixed
|
41
|
+
* Fix issues with recognising :home & :end keys on different terminals
|
42
|
+
* Fix #read_line to work with strings spanning multiple screen widths and allow copy-pasting a long string without repeating prompt
|
43
|
+
* Fix backspace keystroke in cooked mode
|
44
|
+
* Fix history to only save lines in echo mode
|
45
|
+
|
46
|
+
## [v0.1.0] - 2017-08-30
|
47
|
+
|
48
|
+
* Initial implementation and release
|
49
|
+
|
50
|
+
[v0.5.0]: https://github.com/piotrmurach/tty-reader/compare/v0.4.0...v0.5.0
|
51
|
+
[v0.4.0]: https://github.com/piotrmurach/tty-reader/compare/v0.3.0...v0.4.0
|
52
|
+
[v0.3.0]: https://github.com/piotrmurach/tty-reader/compare/v0.2.0...v0.3.0
|
53
|
+
[v0.2.0]: https://github.com/piotrmurach/tty-reader/compare/v0.1.0...v0.2.0
|
54
|
+
[v0.1.0]: https://github.com/piotrmurach/tty-reader/compare/v0.1.0
|
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
<div align="center">
|
2
|
+
<a href="https://piotrmurach.github.io/tty" target="_blank"><img width="130" src="https://cdn.rawgit.com/piotrmurach/tty/master/images/tty.png" alt="tty logo" /></a>
|
3
|
+
</div>
|
4
|
+
|
1
5
|
# TTY::Reader [][gitter]
|
2
6
|
|
3
7
|
[][gem]
|
@@ -15,10 +19,12 @@
|
|
15
19
|
[coverage]: https://coveralls.io/github/piotrmurach/tty-reader
|
16
20
|
[inchpages]: http://inch-ci.org/github/piotrmurach/tty-reader
|
17
21
|
|
18
|
-
> A pure Ruby library that provides a set of methods for processing keyboard input in character, line and multiline modes.
|
22
|
+
> A pure Ruby library that provides a set of methods for processing keyboard input in character, line and multiline modes. It maintains history of entered input with an ability to recall and re-edit those inputs. It lets you register to listen for keystroke events and trigger custom key events yourself.
|
19
23
|
|
20
24
|
**TTY::Reader** provides independent reader component for [TTY](https://github.com/piotrmurach/tty) toolkit.
|
21
25
|
|
26
|
+

|
27
|
+
|
22
28
|
## Compatibility
|
23
29
|
|
24
30
|
The `tty-reader` is not compatible with the GNU Readline and doesn't aim to be. It originated from [tty-prompt](https://github.com/piotrmurach/tty-prompt) project to provide flexibility, independence from underlying operating system and Ruby like API interface for creating different prompts.
|
@@ -28,11 +34,11 @@ The `tty-reader` is not compatible with the GNU Readline and doesn't aim to be.
|
|
28
34
|
## Features
|
29
35
|
|
30
36
|
* Pure Ruby
|
31
|
-
*
|
32
|
-
*
|
33
|
-
* Reading multiline input
|
34
|
-
*
|
35
|
-
*
|
37
|
+
* Reading [single keypress](#21-read_keypress)
|
38
|
+
* [Line editing](#22-read_line)
|
39
|
+
* Reading [multiline input](#23-read_multiline)
|
40
|
+
* Ability to [register](#24-on) for keystroke events
|
41
|
+
* Track input [history](#32-track_history)
|
36
42
|
* No global state
|
37
43
|
* Works on Linux, OS X, FreeBSD and Windows
|
38
44
|
* Supports Ruby versions `>= 2.0.0` & JRuby
|
@@ -72,10 +78,31 @@ Or install it yourself as:
|
|
72
78
|
|
73
79
|
## Usage
|
74
80
|
|
81
|
+
In just a few lines you can recreate IRB prompt.
|
82
|
+
|
83
|
+
Initialize the reader:
|
84
|
+
|
75
85
|
```ruby
|
76
86
|
reader = TTY::Reader.new
|
77
87
|
```
|
78
88
|
|
89
|
+
Then register to listen for key events, in this case listen for `Ctrl-X` or `Esc` keys to exit:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
reader.on(:keyctrl_x, :keyescape) do
|
93
|
+
puts "Exiting..."
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
Finally, keep asking user for line input with a `=>` as a prompt:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
loop do
|
102
|
+
reader.read_line('=> ')
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
79
106
|
## API
|
80
107
|
|
81
108
|
### 2.1 read_keypress
|
@@ -109,7 +136,7 @@ Any non-interpreted characters received are written back to terminal, however yo
|
|
109
136
|
reader.read_line(echo: false)
|
110
137
|
```
|
111
138
|
|
112
|
-
You can also provide a line prefix displayed before input by passing it as a first
|
139
|
+
You can also provide a line prefix displayed before input by passing it as a first argument:
|
113
140
|
|
114
141
|
```ruby
|
115
142
|
reader.read_line(">> ")
|
@@ -133,7 +160,7 @@ If you wish for the keystrokes to be interpreted by the terminal instead, use so
|
|
133
160
|
reader.read_line(raw: false)
|
134
161
|
```
|
135
162
|
|
136
|
-
You can also provide a
|
163
|
+
You can also provide a line prefix displayed before input by passing a string as a first argument:
|
137
164
|
|
138
165
|
```ruby
|
139
166
|
reader.read_multiline(">> ")
|
@@ -141,13 +168,27 @@ reader.read_multiline(">> ")
|
|
141
168
|
|
142
169
|
### 2.4 on
|
143
170
|
|
144
|
-
You can register to listen on a key pressed events. This can be done by calling `on` with a event name:
|
171
|
+
You can register to listen on a key pressed events. This can be done by calling `on` with a event name(s):
|
145
172
|
|
146
173
|
```ruby
|
147
174
|
reader.on(:keypress) { |event| .... }
|
148
175
|
```
|
149
176
|
|
150
|
-
|
177
|
+
or listen for multiple events:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
reader.on(:keyctrl_x, :keyescape) { |event| ... }
|
181
|
+
```
|
182
|
+
|
183
|
+
The `KeyEvent` object is yielded to a block whenever a particular key event fires. The event responds to:
|
184
|
+
|
185
|
+
* `key` - key pressed
|
186
|
+
* `value` - value of the key pressed
|
187
|
+
* `line` - the content of the currently edited line, empty otherwise
|
188
|
+
|
189
|
+
The `value` returns the actual key pressed and the `line` the content for the currently edited line or is empty.
|
190
|
+
|
191
|
+
The `key` is an object that responds to following messages:
|
151
192
|
|
152
193
|
* `name` - the name of the event such as :up, :down, letter or digit
|
153
194
|
* `meta` - true if event is non-standard key associated
|
@@ -349,4 +390,4 @@ Everyone interacting in the TTY::Reader project’s codebases, issue trackers, c
|
|
349
390
|
|
350
391
|
## Copyright
|
351
392
|
|
352
|
-
Copyright (c) 2017
|
393
|
+
Copyright (c) 2017 Piotr Murach. See LICENSE for further details.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'tty-reader'
|
3
|
+
|
4
|
+
input = StringIO.new("a")
|
5
|
+
output = StringIO.new
|
6
|
+
$stdin = input
|
7
|
+
reader = TTY::Reader.new(input, output)
|
8
|
+
|
9
|
+
Benchmark.ips do |x|
|
10
|
+
x.report('getc') do
|
11
|
+
input.rewind
|
12
|
+
$stdin.getc
|
13
|
+
end
|
14
|
+
|
15
|
+
x.report('read_char') do
|
16
|
+
input.rewind
|
17
|
+
reader.read_char
|
18
|
+
end
|
19
|
+
|
20
|
+
x.compare!
|
21
|
+
end
|
22
|
+
|
23
|
+
# v0.1.0
|
24
|
+
#
|
25
|
+
# Calculating -------------------------------------
|
26
|
+
# getc 52462 i/100ms
|
27
|
+
# read_char 751 i/100ms
|
28
|
+
# -------------------------------------------------
|
29
|
+
# getc 2484819.4 (±4.1%) i/s - 12433494 in 5.013438s
|
30
|
+
# read_char 7736.4 (±2.9%) i/s - 39052 in 5.052628s
|
31
|
+
#
|
32
|
+
# Comparison:
|
33
|
+
# getc: 2484819.4 i/s
|
34
|
+
# read_char: 7736.4 i/s - 321.19x slower
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'tty-reader'
|
3
|
+
|
4
|
+
input = StringIO.new("abc\n")
|
5
|
+
output = StringIO.new
|
6
|
+
$stdin = input
|
7
|
+
reader = TTY::Reader.new(input, output)
|
8
|
+
|
9
|
+
Benchmark.ips do |x|
|
10
|
+
x.report('gets') do
|
11
|
+
input.rewind
|
12
|
+
$stdin.gets
|
13
|
+
end
|
14
|
+
|
15
|
+
x.report('read_line') do
|
16
|
+
input.rewind
|
17
|
+
reader.read_line
|
18
|
+
end
|
19
|
+
|
20
|
+
x.compare!
|
21
|
+
end
|
22
|
+
|
23
|
+
# v0.1.0
|
24
|
+
#
|
25
|
+
# Calculating -------------------------------------
|
26
|
+
# gets 51729 i/100ms
|
27
|
+
# read_line 164 i/100ms
|
28
|
+
# -------------------------------------------------
|
29
|
+
# gets 1955255.2 (±3.7%) i/s - 9776781 in 5.008004s
|
30
|
+
# read_line 1215.1 (±33.1%) i/s - 5248 in 5.066569s
|
31
|
+
#
|
32
|
+
# Comparison:
|
33
|
+
# gets: 1955255.2 i/s
|
34
|
+
# read_line: 1215.1 i/s - 1609.19x slower
|
data/examples/shell.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require_relative '../lib/tty-reader'
|
2
2
|
|
3
3
|
puts "*** TTY::Reader Shell ***"
|
4
|
-
puts "Press Ctrl-X to exit"
|
4
|
+
puts "Press Ctrl-X or ESC to exit"
|
5
5
|
|
6
6
|
reader = TTY::Reader.new
|
7
7
|
|
8
|
-
reader.on(:keyctrl_x) { puts "Exiting..."; exit }
|
8
|
+
reader.on(:keyctrl_x, :keyescape) { puts "Exiting..."; exit }
|
9
9
|
|
10
10
|
loop do
|
11
11
|
reader.read_line('=> ')
|
data/lib/tty-reader.rb
CHANGED
data/lib/tty/reader.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'tty-cursor'
|
@@ -202,8 +201,10 @@ module TTY
|
|
202
201
|
}
|
203
202
|
|
204
203
|
while console.escape_codes.any?(&condition)
|
205
|
-
get_codes(options, codes)
|
204
|
+
char_codes = get_codes(options.merge(nonblock: true), codes)
|
205
|
+
break if char_codes.nil?
|
206
206
|
end
|
207
|
+
|
207
208
|
codes
|
208
209
|
end
|
209
210
|
|
@@ -222,16 +223,18 @@ module TTY
|
|
222
223
|
# @api public
|
223
224
|
def read_line(prompt = '', **options)
|
224
225
|
opts = { echo: true, raw: true }.merge(options)
|
225
|
-
line = Line.new(prompt
|
226
|
+
line = Line.new(prompt: prompt)
|
226
227
|
screen_width = TTY::Screen.width
|
227
228
|
|
228
229
|
output.print(line.prompt)
|
229
230
|
|
230
231
|
while (codes = get_codes(opts)) && (code = codes[0])
|
231
232
|
char = codes.pack('U*')
|
232
|
-
trigger_key_event(char)
|
233
233
|
|
234
|
-
|
234
|
+
if [:ctrl_d, :ctrl_z].include?(console.keys[char])
|
235
|
+
trigger_key_event(char, line: line.to_s)
|
236
|
+
break
|
237
|
+
end
|
235
238
|
|
236
239
|
if opts[:raw] && opts[:echo]
|
237
240
|
clear_display(line, screen_width)
|
@@ -283,6 +286,8 @@ module TTY
|
|
283
286
|
end
|
284
287
|
end
|
285
288
|
|
289
|
+
trigger_key_event(char, line: line.to_s)
|
290
|
+
|
286
291
|
if [CARRIAGE_RETURN, NEWLINE].include?(code)
|
287
292
|
output.puts unless opts[:echo]
|
288
293
|
break
|
@@ -421,8 +426,8 @@ module TTY
|
|
421
426
|
# @return [nil]
|
422
427
|
#
|
423
428
|
# @api private
|
424
|
-
def trigger_key_event(char)
|
425
|
-
event = KeyEvent.from(console.keys, char)
|
429
|
+
def trigger_key_event(char, line: '')
|
430
|
+
event = KeyEvent.from(console.keys, char, line)
|
426
431
|
trigger(:"key#{event.key.name}", event) if event.trigger?
|
427
432
|
trigger(:keypress, event)
|
428
433
|
end
|
data/lib/tty/reader/console.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require_relative 'keys'
|
@@ -42,8 +41,16 @@ module TTY
|
|
42
41
|
# @api private
|
43
42
|
def get_char(options)
|
44
43
|
mode.raw(options[:raw]) do
|
45
|
-
mode.echo(options[:echo])
|
44
|
+
mode.echo(options[:echo]) do
|
45
|
+
if options[:nonblock]
|
46
|
+
input.read_nonblock(1)
|
47
|
+
else
|
48
|
+
input.getc
|
49
|
+
end
|
50
|
+
end
|
46
51
|
end
|
52
|
+
rescue IO::WaitReadable, EOFError
|
53
|
+
# no more bytes to read
|
47
54
|
end
|
48
55
|
|
49
56
|
protected
|
data/lib/tty/reader/history.rb
CHANGED
data/lib/tty/reader/key_event.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require_relative 'keys'
|
@@ -17,7 +16,7 @@ module TTY
|
|
17
16
|
# Represents key event emitted during keyboard press
|
18
17
|
#
|
19
18
|
# @api public
|
20
|
-
class KeyEvent < Struct.new(:value, :
|
19
|
+
class KeyEvent < Struct.new(:key, :value, :line)
|
21
20
|
# Create key event from read input codes
|
22
21
|
#
|
23
22
|
# @param [Hash[Symbol]] keys
|
@@ -27,7 +26,7 @@ module TTY
|
|
27
26
|
# @return [KeyEvent]
|
28
27
|
#
|
29
28
|
# @api public
|
30
|
-
def self.from(keys, char)
|
29
|
+
def self.from(keys, char, line = '')
|
31
30
|
key = Key.new
|
32
31
|
key.name = (name = keys[char]) ? name : :ignore
|
33
32
|
|
@@ -43,7 +42,7 @@ module TTY
|
|
43
42
|
key.ctrl = true
|
44
43
|
end
|
45
44
|
|
46
|
-
new(char,
|
45
|
+
new(key, char, line)
|
47
46
|
end
|
48
47
|
|
49
48
|
# Check if key event can be triggered
|
data/lib/tty/reader/keys.rb
CHANGED
data/lib/tty/reader/line.rb
CHANGED
@@ -30,13 +30,19 @@ module TTY
|
|
30
30
|
# @api public
|
31
31
|
attr_reader :mode
|
32
32
|
|
33
|
+
# The prompt displayed before input
|
34
|
+
# @api public
|
33
35
|
attr_reader :prompt
|
34
36
|
|
35
|
-
|
37
|
+
# Create a Line instance
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def initialize(text = '', prompt: '')
|
36
41
|
@prompt = prompt.dup
|
37
42
|
@text = text.dup
|
38
43
|
@cursor = [0, @text.length].max
|
39
44
|
@mode = :edit
|
45
|
+
|
40
46
|
yield self if block_given?
|
41
47
|
end
|
42
48
|
|
@@ -128,6 +134,9 @@ module TTY
|
|
128
134
|
# @param [Integer] i
|
129
135
|
# the index to insert at
|
130
136
|
#
|
137
|
+
# @param [String] chars
|
138
|
+
# the characters to insert
|
139
|
+
#
|
131
140
|
# @example
|
132
141
|
# text = 'aaa'
|
133
142
|
# line[5]= 'b'
|
@@ -136,6 +145,7 @@ module TTY
|
|
136
145
|
# @api public
|
137
146
|
def []=(i, chars)
|
138
147
|
edit_mode
|
148
|
+
|
139
149
|
if i.is_a?(Range)
|
140
150
|
@text[i] = chars
|
141
151
|
@cursor += chars.length
|
@@ -145,10 +155,7 @@ module TTY
|
|
145
155
|
if i <= 0
|
146
156
|
before_text = ''
|
147
157
|
after_text = @text.dup
|
148
|
-
elsif i
|
149
|
-
before_text = @text.dup
|
150
|
-
after_text = ''
|
151
|
-
elsif i > @text.length - 1
|
158
|
+
elsif i > @text.length - 1 # insert outside of line input
|
152
159
|
before_text = @text.dup
|
153
160
|
after_text = ?\s * (i - @text.length)
|
154
161
|
@cursor += after_text.length
|