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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d01c5aa8afa041b5ceae747d42f8e94a0fb5dd63dc19f6cff8e9165caa4b014b
4
- data.tar.gz: b37800731d9ea2f8c25658d2d8e37069b7a253fa2229214603b79e165ece3ede
3
+ metadata.gz: 995b414e2605c556220168d30b3f894771e021f2956aa75821fb3504f5ae6589
4
+ data.tar.gz: 4bfa84cd59cab3b14410d4eac314ae819765d94230c1c9b2a1ce94c09e13c80d
5
5
  SHA512:
6
- metadata.gz: e64edab17527852f8fe1caba07554adbcc9040b1cd56472c23d4b0648215bc7fcf1ad159c6cd5de6dcd79e4606b9f199da2b461ade1ed5519f3f27f3038d6189
7
- data.tar.gz: f68401fc9bcc6e9227e58562b02cf20e4ee1a88614f87299acdcd374105daf80de4bdcce4d6dfbc12dc131139b44716070050b55f7127e2a963904f8f0a7006f
6
+ metadata.gz: 061eeb4dc0698c1ff226ad3a2bc9fe69e4c519fe455642bda6bb9675e758e52c385335bb1604256f2002c75f6b2ba8528007e8df9d332815df8f072b75bd1388
7
+ data.tar.gz: e964e5cf12b271ce1fee675afc1d348f7f6c59a01f007580936c2544e672f784a1297e0f7f8ee6acce2d6ee4f867ca32b2142d3d376f5a4a28c82e2ab6521713
@@ -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. In addition it maintains history of entered input with an ability to recall and re-edit those inputs and register to listen for keystroke events.
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
- * Line editing
32
- * Reading single keypress
33
- * Reading multiline input
34
- * History management
35
- * Ability to register for keystroke events
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 aargument:
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 linke prefix displayed before input by passing a string as a first argument:
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
- The event object is yielded to a block whenever particular key event fires. The event has `key` and `value` methods. Further, the `key` responds to following messages:
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-2018 Piotr Murach. See LICENSE for further details.
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
@@ -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('=> ')
@@ -1,3 +1 @@
1
- # encoding: utf-8
2
-
3
1
  require_relative 'tty/reader'
@@ -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
- break if [:ctrl_d, :ctrl_z].include?(console.keys[char])
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
@@ -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]) { input.getc }
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
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  require 'forwardable'
@@ -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, :key)
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, key)
45
+ new(key, char, line)
47
46
  end
48
47
 
49
48
  # Check if key event can be triggered
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module TTY
@@ -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
- def initialize(prompt, text = '')
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 == @text.length - 1
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