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 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