tty-reader 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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](https://badges.gitter.im/Join%20Chat.svg)][gitter]
|
2
6
|
|
3
7
|
[![Gem Version](https://badge.fury.io/rb/tty-reader.svg)][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
|
+
![](assets/shell.gif)
|
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
|