tty-reader 0.4.0 → 0.9.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: 7766384b749f39d5646ce4ff20d95c1bdfdaac5af096d4a996a7a347379874ee
4
+ data.tar.gz: c5a85b72fae90d018f79acdd9303c9926b75236fb67a2b24e641f6737a001372
5
5
  SHA512:
6
- metadata.gz: e64edab17527852f8fe1caba07554adbcc9040b1cd56472c23d4b0648215bc7fcf1ad159c6cd5de6dcd79e4606b9f199da2b461ade1ed5519f3f27f3038d6189
7
- data.tar.gz: f68401fc9bcc6e9227e58562b02cf20e4ee1a88614f87299acdcd374105daf80de4bdcce4d6dfbc12dc131139b44716070050b55f7127e2a963904f8f0a7006f
6
+ metadata.gz: 74bd44d949d086d8debe53bdd571755b263fce26d991dd7798d45fbb24513cdadea0a29c31e3388a65ce8f81f3b3514955d69c44c30af87465ba32933f5f6997
7
+ data.tar.gz: 4ad0c8d086506a2672f2a8bb8db1fbd9f951e2f1f9200f4c258e82967cf61bbea209a33fdd86aa4cfb99adf4e5b7fcdcf6e5b0f992da6fe9956daa5d5aade238
@@ -0,0 +1,99 @@
1
+ # Change log
2
+
3
+ ## [v0.9.0] - 2020-12-08
4
+
5
+ ### Added
6
+ * Add buffer to save input when traversing history and restore it back
7
+ similar to shell
8
+
9
+ ### Changed
10
+ * Pressing :down no longer erases the #read_line input if history is disabled by Charles Pence (@pencechp)
11
+ * Change Reader initializer to use keyword arguments in place of options hash
12
+ * Change history to only exclude empty lines without any space or invisible characters
13
+ * Change all input reading methods to use explicit keyword arguments
14
+
15
+ ### Fix
16
+ * Fix #read_multiline :value parameter to insert content only once in the first line
17
+
18
+ ## [v0.8.0] - 2020-06-28
19
+
20
+ ### Changed
21
+ * Change gemspec to load version directly and remove test artefacts
22
+ * Change to update tty-screen dependency
23
+ * Change to remove bundler dev dependency and relax wisper version
24
+
25
+ ## [v0.7.0] - 2019-11-24
26
+
27
+ ### Added
28
+ * Add support for a multi-line prompt by Katelyn Schiesser(@slowbro)
29
+ * Add metadata to gemspec
30
+
31
+ ## [v0.6.0] - 2019-05-27
32
+
33
+ ### Added
34
+ * Add :value option to #read_line to allow pre-populating of line content
35
+
36
+ ### Changed
37
+ * Change to make InputInterrupt to derive from Interrupt by Samuel Williams(@ioquatix)
38
+ * Change #read_line to trigger before line is printed to allow for line changes in key callbacks
39
+ * Change Console#get_char :nonblock option to wait for readable input without blocking
40
+ * Change to remove bundler version constraints
41
+ * Change to update tty-screen dependency
42
+ * Change to update tty-cursor dependency
43
+
44
+ ## [v0.5.0] - 2018-11-24
45
+
46
+ ### Added
47
+ * Add KeyEvent#line to expose current line in key event callbacks
48
+
49
+ ### Fixed
50
+ * Fix Esc key by differentiating between escaped keys and actual escape input
51
+ * Fix line editing to correctly insert next to last character
52
+
53
+ ## [v0.4.0] - 2018-08-05
54
+
55
+ ### Changed
56
+ * Change to update tty-screen & tty-cursor dependencies
57
+
58
+ ## [v0.3.0] - 2018-04-29
59
+
60
+ ### Added
61
+ * Add Reader#unsubscribe to allow stop listening to local key events
62
+
63
+ ### Changed
64
+ * Change Reader#subscribe to allow to listening for key events only inside a block
65
+ * Change to group xterm keys for navigation
66
+
67
+ ## [v0.2.0] - 2018-01-01
68
+
69
+ ### Added
70
+ * Add home & end keys support in #read_line
71
+ * Add tty-screen & tty-cursor dependencies
72
+
73
+ ### Changed
74
+ * Change Codes to Keys and inverse keys lookup to allow for different system keys matching same name.
75
+ * Change Reader#initialize to only accept options and make input and output options as well.
76
+ * Change #read_line to print newline character in noecho mode
77
+ * Change Reader::Line to include prompt prefix
78
+ * Change Reader#initialize to only accept options in place of positional arguments
79
+ * Change Reader to expose history options
80
+
81
+ ### Fixed
82
+ * Fix issues with recognising :home & :end keys on different terminals
83
+ * Fix #read_line to work with strings spanning multiple screen widths and allow copy-pasting a long string without repeating prompt
84
+ * Fix backspace keystroke in cooked mode
85
+ * Fix history to only save lines in echo mode
86
+
87
+ ## [v0.1.0] - 2017-08-30
88
+
89
+ * Initial implementation and release
90
+
91
+ [v0.9.0]: https://github.com/piotrmurach/tty-reader/compare/v0.8.0...v0.9.0
92
+ [v0.8.0]: https://github.com/piotrmurach/tty-reader/compare/v0.7.0...v0.8.0
93
+ [v0.7.0]: https://github.com/piotrmurach/tty-reader/compare/v0.6.0...v0.7.0
94
+ [v0.6.0]: https://github.com/piotrmurach/tty-reader/compare/v0.5.0...v0.6.0
95
+ [v0.5.0]: https://github.com/piotrmurach/tty-reader/compare/v0.4.0...v0.5.0
96
+ [v0.4.0]: https://github.com/piotrmurach/tty-reader/compare/v0.3.0...v0.4.0
97
+ [v0.3.0]: https://github.com/piotrmurach/tty-reader/compare/v0.2.0...v0.3.0
98
+ [v0.2.0]: https://github.com/piotrmurach/tty-reader/compare/v0.1.0...v0.2.0
99
+ [v0.1.0]: https://github.com/piotrmurach/tty-reader/compare/v0.1.0
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Piotr Murach
3
+ Copyright (c) 2017 Piotr Murach (https://piotrmurach.com)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
+ <div align="center">
2
+ <a href="https://ttytoolkit.org"><img width="130" src="https://github.com/piotrmurach/tty/raw/master/images/tty.png" alt="TTY Toolkit 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]
4
- [![Build Status](https://secure.travis-ci.org/piotrmurach/tty-reader.svg?branch=master)][travis]
8
+ [![Actions CI](https://github.com/piotrmurach/tty-reader/workflows/CI/badge.svg?branch=master)][gh_actions_ci]
5
9
  [![Build status](https://ci.appveyor.com/api/projects/status/cj4owy2vlty2q1ko?svg=true)][appveyor]
6
10
  [![Maintainability](https://api.codeclimate.com/v1/badges/2f68d5e8ecc271bda820/maintainability)][codeclimate]
7
11
  [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-reader/badge.svg)][coverage]
@@ -9,16 +13,19 @@
9
13
 
10
14
  [gitter]: https://gitter.im/piotrmurach/tty
11
15
  [gem]: http://badge.fury.io/rb/tty-reader
16
+ [gh_actions_ci]: https://github.com/piotrmurach/tty-reader/actions?query=workflow%3ACI
12
17
  [travis]: http://travis-ci.org/piotrmurach/tty-reader
13
18
  [appveyor]: https://ci.appveyor.com/project/piotrmurach/tty-reader
14
19
  [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-reader/maintainability
15
20
  [coverage]: https://coveralls.io/github/piotrmurach/tty-reader
16
21
  [inchpages]: http://inch-ci.org/github/piotrmurach/tty-reader
17
22
 
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.
23
+ > 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
24
 
20
25
  **TTY::Reader** provides independent reader component for [TTY](https://github.com/piotrmurach/tty) toolkit.
21
26
 
27
+ ![](assets/shell.gif)
28
+
22
29
  ## Compatibility
23
30
 
24
31
  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 +35,11 @@ The `tty-reader` is not compatible with the GNU Readline and doesn't aim to be.
28
35
  ## Features
29
36
 
30
37
  * Pure Ruby
31
- * Line editing
32
- * Reading single keypress
33
- * Reading multiline input
34
- * History management
35
- * Ability to register for keystroke events
38
+ * Reading [single keypress](#21-read_keypress)
39
+ * [Line editing](#22-read_line)
40
+ * Reading [multiline input](#23-read_multiline)
41
+ * Ability to [register](#24-on) for keystroke events
42
+ * Track input [history](#32-track_history)
36
43
  * No global state
37
44
  * Works on Linux, OS X, FreeBSD and Windows
38
45
  * Supports Ruby versions `>= 2.0.0` & JRuby
@@ -42,7 +49,7 @@ The `tty-reader` is not compatible with the GNU Readline and doesn't aim to be.
42
49
  Add this line to your application's Gemfile:
43
50
 
44
51
  ```ruby
45
- gem 'tty-reader'
52
+ gem "tty-reader"
46
53
  ```
47
54
 
48
55
  And then execute:
@@ -72,10 +79,31 @@ Or install it yourself as:
72
79
 
73
80
  ## Usage
74
81
 
82
+ In just a few lines you can recreate IRB prompt.
83
+
84
+ Initialize the reader:
85
+
75
86
  ```ruby
76
87
  reader = TTY::Reader.new
77
88
  ```
78
89
 
90
+ Then register to listen for key events, in this case listen for `Ctrl-X` or `Esc` keys to exit:
91
+
92
+ ```ruby
93
+ reader.on(:keyctrl_x, :keyescape) do
94
+ puts "Exiting..."
95
+ exit
96
+ end
97
+ ```
98
+
99
+ Finally, keep asking user for line input with a `=>` as a prompt:
100
+
101
+ ```ruby
102
+ loop do
103
+ reader.read_line("=> ")
104
+ end
105
+ ```
106
+
79
107
  ## API
80
108
 
81
109
  ### 2.1 read_keypress
@@ -85,6 +113,7 @@ To read a single key stroke from the user use `read_char` or `read_keypress`:
85
113
  ```ruby
86
114
  reader.read_char
87
115
  reader.read_keypress
116
+ reader.read_keypress(nonblock: true)
88
117
  ```
89
118
 
90
119
  ### 2.2 read_line
@@ -109,11 +138,18 @@ Any non-interpreted characters received are written back to terminal, however yo
109
138
  reader.read_line(echo: false)
110
139
  ```
111
140
 
112
- You can also provide a line prefix displayed before input by passing it as a first aargument:
141
+ You can also provide a line prefix displayed before input by passing it as a first argument:
113
142
 
114
143
  ```ruby
115
144
  reader.read_line(">> ")
116
- # >> input goes here ...
145
+ # >>
146
+ ```
147
+
148
+ To pre-populate the line content for editing use `:value` option:
149
+
150
+ ```ruby
151
+ reader.read_line("> ", value: "edit me")
152
+ # > edit me
117
153
  ```
118
154
 
119
155
  ### 2.3 read_multiline
@@ -133,7 +169,7 @@ If you wish for the keystrokes to be interpreted by the terminal instead, use so
133
169
  reader.read_line(raw: false)
134
170
  ```
135
171
 
136
- You can also provide a linke prefix displayed before input by passing a string as a first argument:
172
+ You can also provide a line prefix displayed before input by passing a string as a first argument:
137
173
 
138
174
  ```ruby
139
175
  reader.read_multiline(">> ")
@@ -141,13 +177,27 @@ reader.read_multiline(">> ")
141
177
 
142
178
  ### 2.4 on
143
179
 
144
- You can register to listen on a key pressed events. This can be done by calling `on` with a event name:
180
+ You can register to listen on a key pressed events. This can be done by calling `on` with a event name(s):
145
181
 
146
182
  ```ruby
147
183
  reader.on(:keypress) { |event| .... }
148
184
  ```
149
185
 
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:
186
+ or listen for multiple events:
187
+
188
+ ```ruby
189
+ reader.on(:keyctrl_x, :keyescape) { |event| ... }
190
+ ```
191
+
192
+ The `KeyEvent` object is yielded to a block whenever a particular key event fires. The event responds to:
193
+
194
+ * `key` - key pressed
195
+ * `value` - value of the key pressed
196
+ * `line` - the content of the currently edited line, empty otherwise
197
+
198
+ The `value` returns the actual key pressed and the `line` the content for the currently edited line or is empty.
199
+
200
+ The `key` is an object that responds to following messages:
151
201
 
152
202
  * `name` - the name of the event such as :up, :down, letter or digit
153
203
  * `meta` - true if event is non-standard key associated
@@ -158,11 +208,11 @@ For example, to add listen to vim like navigation keys, one would do the followi
158
208
 
159
209
  ```ruby
160
210
  reader.on(:keypress) do |event|
161
- if event.value == 'j'
211
+ if event.value == "j"
162
212
  ...
163
213
  end
164
214
 
165
- if event.value == 'k'
215
+ if event.value == "k"
166
216
  ...
167
217
  end
168
218
  end
@@ -171,8 +221,8 @@ end
171
221
  You can subscribe to more than one event:
172
222
 
173
223
  ```ruby
174
- prompt.on(:keypress) { |key| ... }
175
- .on(:keydown) { |key| ... }
224
+ reader.on(:keypress) { |event| ... }
225
+ .on(:keydown) { |event| ... }
176
226
  ```
177
227
 
178
228
  ### 2.5 subscribe
@@ -189,13 +239,13 @@ class MyListener
189
239
  end
190
240
  ```
191
241
 
192
- Then subcribing is done:
242
+ Then subscribing is done:
193
243
 
194
244
  ```ruby
195
245
  reader.subscribe(MyListener.new)
196
246
  ```
197
247
 
198
- Alternatively, `subscribe` allows you to listen to events only for the dueration of block execution like so:
248
+ Alternatively, `subscribe` allows you to listen to events only for the duration of block execution like so:
199
249
 
200
250
  ```ruby
201
251
  reader.subscribe(MyListener) do
@@ -225,10 +275,10 @@ To add vim bindings for line editing you could discern between alphanumeric inpu
225
275
 
226
276
  ```ruby
227
277
  reader.on(:keypress) do |event|
228
- if event.value == 'j'
278
+ if event.value == "j"
229
279
  reader.trigger(:keydown)
230
280
  end
231
- if evevnt.value == 'k'
281
+ if evevnt.value == "k"
232
282
  reader.trigger(:keyup)
233
283
  end
234
284
  end
@@ -249,7 +299,7 @@ The available key events for character input are:
249
299
  * `:keyalpha`
250
300
  * `:keynum`
251
301
 
252
- The navigation relted key events are:
302
+ The navigation related key events are:
253
303
 
254
304
  * `:keydown`
255
305
  * `:keyup`
@@ -292,7 +342,7 @@ reader = TTY::Reader.new(interrupt: :signal)
292
342
 
293
343
  ### 3.2. `:track_history`
294
344
 
295
- The `read_line` and `read_multiline` provide history buffer that tracks all the lines entered during `TTY::Reader.new` interactions. The history buffer provides previoius or next lines when user presses up/down arrows respectively. However, if you wish to disable this behaviour use `:track_history` option like so:
345
+ The `read_line` and `read_multiline` provide history buffer that tracks all the lines entered during `TTY::Reader.new` interactions. The history buffer provides previous or next lines when user presses up/down arrows respectively. However, if you wish to disable this behaviour use `:track_history` option like so:
296
346
 
297
347
  ```ruby
298
348
  reader = TTY::Reader.new(track_history: false)
@@ -316,7 +366,7 @@ reader = TTY::Reader.new(history_duplicates: false)
316
366
 
317
367
  ### 3.5. `:history_exclude`
318
368
 
319
- This option allows you to exclude lines from being stored in history. It accepts a `Proc` with a line as a first argument. By default it is set to exlude empty lines. To change this:
369
+ This option allows you to exclude lines from being stored in history. It accepts a `Proc` with a line as a first argument. By default it is set to exclude empty lines. To change this:
320
370
 
321
371
  ```ruby
322
372
  reader = TTY::Reader.new(history_exclude: ->(line) { ... })
@@ -349,4 +399,4 @@ Everyone interacting in the TTY::Reader project’s codebases, issue trackers, c
349
399
 
350
400
  ## Copyright
351
401
 
352
- Copyright (c) 2017-2018 Piotr Murach. See LICENSE for further details.
402
+ Copyright (c) 2017 Piotr Murach. See LICENSE for further details.
@@ -1,3 +1 @@
1
- # encoding: utf-8
2
-
3
1
  require_relative 'tty/reader'
@@ -1,16 +1,15 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'tty-cursor'
5
- require 'tty-screen'
6
- require 'wisper'
3
+ require "tty-cursor"
4
+ require "tty-screen"
5
+ require "wisper"
7
6
 
8
- require_relative 'reader/history'
9
- require_relative 'reader/line'
10
- require_relative 'reader/key_event'
11
- require_relative 'reader/console'
12
- require_relative 'reader/win_console'
13
- require_relative 'reader/version'
7
+ require_relative "reader/history"
8
+ require_relative "reader/line"
9
+ require_relative "reader/key_event"
10
+ require_relative "reader/console"
11
+ require_relative "reader/win_console"
12
+ require_relative "reader/version"
14
13
 
15
14
  module TTY
16
15
  # A class responsible for reading character input from STDIN
@@ -21,10 +20,19 @@ module TTY
21
20
  class Reader
22
21
  include Wisper::Publisher
23
22
 
23
+ # Key codes
24
+ CARRIAGE_RETURN = 13
25
+ NEWLINE = 10
26
+ BACKSPACE = 8
27
+ DELETE = 127
28
+
29
+ # Keys that terminate input
30
+ EXIT_KEYS = [:ctrl_d, :ctrl_z]
31
+
24
32
  # Raised when the user hits the interrupt key(Control-C)
25
33
  #
26
34
  # @api public
27
- InputInterrupt = Class.new(StandardError)
35
+ InputInterrupt = Class.new(Interrupt)
28
36
 
29
37
  # Check if Windowz mode
30
38
  #
@@ -32,7 +40,7 @@ module TTY
32
40
  #
33
41
  # @api public
34
42
  def self.windows?
35
- ::File::ALT_SEPARATOR == '\\'
43
+ ::File::ALT_SEPARATOR == "\\"
36
44
  end
37
45
 
38
46
  attr_reader :input
@@ -48,42 +56,44 @@ module TTY
48
56
 
49
57
  attr_reader :cursor
50
58
 
51
- # Key codes
52
- CARRIAGE_RETURN = 13
53
- NEWLINE = 10
54
- BACKSPACE = 8
55
- DELETE = 127
56
-
57
59
  # Initialize a Reader
58
60
  #
59
61
  # @param [IO] input
60
62
  # the input stream
61
63
  # @param [IO] output
62
64
  # the output stream
63
- # @param [Hash] options
64
- # @option options [Symbol] :interrupt
65
- # handling of Ctrl+C key out of :signal, :exit, :noop
66
- # @option options [Boolean] :track_history
65
+ # @param [Symbol] interrupt
66
+ # the way to handle the Ctrl+C key out of :signal, :exit, :noop
67
+ # @param [Hash] env
68
+ # the environment variables
69
+ # @param [Boolean] track_history
67
70
  # disable line history tracking, true by default
71
+ # @param [Boolean] history_cycle
72
+ # allow cycling through history, false by default
73
+ # @param [Boolean] history_duplicates
74
+ # allow duplicate entires, false by default
75
+ # @param [Proc] history_exclude
76
+ # exclude lines from history, by default all lines are stored
68
77
  #
69
78
  # @api public
70
- def initialize(**options)
71
- @input = options.fetch(:input) { $stdin }
72
- @output = options.fetch(:output) { $stdout }
73
- @interrupt = options.fetch(:interrupt) { :error }
74
- @env = options.fetch(:env) { ENV }
75
-
76
- @track_history = options.fetch(:track_history) { true }
77
- @history_cycle = options.fetch(:history_cycle) { false }
78
- exclude_proc = ->(line) { line.strip == '' }
79
- @history_exclude = options.fetch(:history_exclude) { exclude_proc }
80
- @history_duplicates = options.fetch(:history_duplicates) { false }
81
-
82
- @console = select_console(input)
83
- @history = History.new do |h|
84
- h.cycle = @history_cycle
85
- h.duplicates = @history_duplicates
86
- h.exclude = @history_exclude
79
+ def initialize(input: $stdin, output: $stdout, interrupt: :error,
80
+ env: ENV, track_history: true, history_cycle: false,
81
+ history_exclude: History::DEFAULT_EXCLUDE,
82
+ history_duplicates: false)
83
+ @input = input
84
+ @output = output
85
+ @interrupt = interrupt
86
+ @env = env
87
+ @track_history = track_history
88
+ @history_cycle = history_cycle
89
+ @history_exclude = history_exclude
90
+ @history_duplicates = history_duplicates
91
+
92
+ @console = select_console(input)
93
+ @history = History.new do |h|
94
+ h.cycle = history_cycle
95
+ h.duplicates = history_duplicates
96
+ h.exclude = history_exclude
87
97
  end
88
98
  @stop = false # gathering input
89
99
  @cursor = TTY::Cursor
@@ -132,7 +142,7 @@ module TTY
132
142
  #
133
143
  # @api private
134
144
  def select_console(input)
135
- if self.class.windows? && !env['TTY_TEST']
145
+ if self.class.windows? && !env["TTY_TEST"]
136
146
  WinConsole.new(input)
137
147
  else
138
148
  Console.new(input)
@@ -156,24 +166,26 @@ module TTY
156
166
  output.sync = bufferring
157
167
  end
158
168
 
159
- # Read a keypress including invisible multibyte codes
160
- # and return a character as a string.
169
+ # Read a keypress including invisible multibyte codes and return
170
+ # a character as a string.
161
171
  # Nothing is echoed to the console. This call will block for a
162
172
  # single keypress, but will not wait for Enter to be pressed.
163
173
  #
164
- # @param [Hash[Symbol]] options
165
- # @option options [Boolean] echo
174
+ # @param [Boolean] echo
166
175
  # whether to echo chars back or not, defaults to false
167
- # @option options [Boolean] raw
168
- # whenther raw mode enabled, defaults to true
176
+ # @option [Boolean] raw
177
+ # whenther raw mode is enabled, defaults to true
178
+ # @option [Boolean] nonblock
179
+ # whether to wait for input or not, defaults to false
169
180
  #
170
181
  # @return [String]
171
182
  #
172
183
  # @api public
173
- def read_keypress(options = {})
174
- opts = { echo: false, raw: true }.merge(options)
175
- codes = unbufferred { get_codes(opts) }
176
- char = codes ? codes.pack('U*') : nil
184
+ def read_keypress(echo: false, raw: true, nonblock: false)
185
+ codes = unbufferred do
186
+ get_codes(echo: echo, raw: raw, nonblock: nonblock)
187
+ end
188
+ char = codes ? codes.pack("U*") : nil
177
189
 
178
190
  trigger_key_event(char) if char
179
191
  char
@@ -182,19 +194,24 @@ module TTY
182
194
 
183
195
  # Get input code points
184
196
  #
185
- # @param [Hash[Symbol]] options
197
+ # @param [Boolean] echo
198
+ # whether to echo chars back or not, defaults to false
199
+ # @option [Boolean] raw
200
+ # whenther raw mode is enabled, defaults to true
201
+ # @option [Boolean] nonblock
202
+ # whether to wait for input or not, defaults to false
186
203
  # @param [Array[Integer]] codes
204
+ # the currently read char code points
187
205
  #
188
206
  # @return [Array[Integer]]
189
207
  #
190
208
  # @api private
191
- def get_codes(options = {}, codes = [])
192
- opts = { echo: true, raw: false }.merge(options)
193
- char = console.get_char(opts)
209
+ def get_codes(echo: true, raw: false, nonblock: false, codes: [])
210
+ char = console.get_char(echo: echo, raw: raw, nonblock: nonblock)
194
211
  handle_interrupt if console.keys[char] == :ctrl_c
195
212
  return if char.nil?
196
- codes << char.ord
197
213
 
214
+ codes << char.ord
198
215
  condition = proc { |escape|
199
216
  (codes - escape).empty? ||
200
217
  (escape - codes).empty? &&
@@ -202,8 +219,11 @@ module TTY
202
219
  }
203
220
 
204
221
  while console.escape_codes.any?(&condition)
205
- get_codes(options, codes)
222
+ char_codes = get_codes(echo: echo, raw: raw,
223
+ nonblock: true, codes: codes)
224
+ break if char_codes.nil?
206
225
  end
226
+
207
227
  codes
208
228
  end
209
229
 
@@ -213,43 +233,51 @@ module TTY
213
233
  #
214
234
  # @param [String] prompt
215
235
  # the prompt to display before input
216
- #
236
+ # @param [String] value
237
+ # the value to pre-populate line with
217
238
  # @param [Boolean] echo
218
- # if true echo back characters, output nothing otherwise
239
+ # whether to echo chars back or not, defaults to false
240
+ # @option [Boolean] raw
241
+ # whenther raw mode is enabled, defaults to true
242
+ # @option [Boolean] nonblock
243
+ # whether to wait for input or not, defaults to false
219
244
  #
220
245
  # @return [String]
221
246
  #
222
247
  # @api public
223
- def read_line(prompt = '', **options)
224
- opts = { echo: true, raw: true }.merge(options)
225
- line = Line.new(prompt, '')
248
+ def read_line(prompt = "", value: "", echo: true, raw: true, nonblock: false)
249
+ line = Line.new(value, prompt: prompt)
226
250
  screen_width = TTY::Screen.width
251
+ buffer = ""
227
252
 
228
- output.print(line.prompt)
253
+ output.print(line)
229
254
 
230
- while (codes = get_codes(opts)) && (code = codes[0])
231
- char = codes.pack('U*')
232
- trigger_key_event(char)
255
+ while (codes = get_codes(echo: echo, raw: raw, nonblock: nonblock)) &&
256
+ (code = codes[0])
257
+ char = codes.pack("U*")
233
258
 
234
- break if [:ctrl_d, :ctrl_z].include?(console.keys[char])
259
+ if EXIT_KEYS.include?(console.keys[char])
260
+ trigger_key_event(char, line: line.to_s)
261
+ break
262
+ end
235
263
 
236
- if opts[:raw] && opts[:echo]
264
+ if raw && echo
237
265
  clear_display(line, screen_width)
238
266
  end
239
267
 
240
- if console.keys[char] == :backspace || BACKSPACE == code
268
+ if console.keys[char] == :backspace || code == BACKSPACE
241
269
  if !line.start?
242
270
  line.left
243
271
  line.delete
244
272
  end
245
- elsif console.keys[char] == :delete || DELETE == code
273
+ elsif console.keys[char] == :delete || code == DELETE
246
274
  line.delete
247
275
  elsif console.keys[char].to_s =~ /ctrl_/
248
276
  # skip
249
277
  elsif console.keys[char] == :up
250
278
  line.replace(history_previous) if history_previous?
251
279
  elsif console.keys[char] == :down
252
- line.replace(history_next? ? history_next : '')
280
+ line.replace(history_next? ? history_next : buffer) if track_history?
253
281
  elsif console.keys[char] == :left
254
282
  line.left
255
283
  elsif console.keys[char] == :right
@@ -259,22 +287,26 @@ module TTY
259
287
  elsif console.keys[char] == :end
260
288
  line.move_to_end
261
289
  else
262
- if opts[:raw] && code == CARRIAGE_RETURN
290
+ if raw && code == CARRIAGE_RETURN
263
291
  char = "\n"
264
292
  line.move_to_end
265
293
  end
266
294
  line.insert(char)
295
+ buffer = line.text
267
296
  end
268
297
 
269
- if (console.keys[char] == :backspace || BACKSPACE == code) && opts[:echo]
270
- if opts[:raw]
298
+ if (console.keys[char] == :backspace || code == BACKSPACE) && echo
299
+ if raw
271
300
  output.print("\e[1X") unless line.start?
272
301
  else
273
- output.print(?\s + (line.start? ? '' : ?\b))
302
+ output.print(?\s + (line.start? ? "" : ?\b))
274
303
  end
275
304
  end
276
305
 
277
- if opts[:raw] && opts[:echo]
306
+ # trigger before line is printed to allow for line changes
307
+ trigger_key_event(char, line: line.to_s)
308
+
309
+ if raw && echo
278
310
  output.print(line.to_s)
279
311
  if char == "\n"
280
312
  line.move_to_start
@@ -284,13 +316,16 @@ module TTY
284
316
  end
285
317
 
286
318
  if [CARRIAGE_RETURN, NEWLINE].include?(code)
287
- output.puts unless opts[:echo]
319
+ buffer = ""
320
+ output.puts unless echo
288
321
  break
289
322
  end
290
323
  end
291
- if track_history? && opts[:echo]
324
+
325
+ if track_history? && echo
292
326
  add_to_history(line.text.rstrip)
293
327
  end
328
+
294
329
  line.text
295
330
  end
296
331
 
@@ -341,19 +376,33 @@ module TTY
341
376
  #
342
377
  # @param [String] prompt
343
378
  # the prompt displayed before the input
379
+ # @param [String] value
380
+ # the value to pre-populate line with
381
+ # @param [Boolean] echo
382
+ # whether to echo chars back or not, defaults to false
383
+ # @option [Boolean] raw
384
+ # whenther raw mode is enabled, defaults to true
385
+ # @option [Boolean] nonblock
386
+ # whether to wait for input or not, defaults to false
344
387
  #
345
388
  # @yield [String] line
346
389
  #
347
390
  # @return [Array[String]]
348
391
  #
349
392
  # @api public
350
- def read_multiline(*args)
393
+ def read_multiline(prompt = "", value: "", echo: true, raw: true,
394
+ nonblock: false)
351
395
  @stop = false
352
396
  lines = []
397
+ empty_str = ""
398
+
353
399
  loop do
354
- line = read_line(*args)
355
- break if !line || line == ''
400
+ line = read_line(prompt, value: value, echo: echo, raw: raw,
401
+ nonblock: nonblock)
402
+ value = empty_str unless value.empty? # reset
403
+ break if !line || line == empty_str
356
404
  next if line !~ /\S/ && !@stop
405
+
357
406
  if block_given?
358
407
  yield(line) unless line.to_s.empty?
359
408
  else
@@ -361,6 +410,7 @@ module TTY
361
410
  end
362
411
  break if @stop
363
412
  end
413
+
364
414
  lines
365
415
  end
366
416
  alias read_lines read_multiline
@@ -421,8 +471,8 @@ module TTY
421
471
  # @return [nil]
422
472
  #
423
473
  # @api private
424
- def trigger_key_event(char)
425
- event = KeyEvent.from(console.keys, char)
474
+ def trigger_key_event(char, line: "")
475
+ event = KeyEvent.from(console.keys, char, line)
426
476
  trigger(:"key#{event.key.name}", event) if event.trigger?
427
477
  trigger(:keypress, event)
428
478
  end
@@ -433,13 +483,13 @@ module TTY
433
483
  def handle_interrupt
434
484
  case @interrupt
435
485
  when :signal
436
- Process.kill('SIGINT', Process.pid)
486
+ Process.kill("SIGINT", Process.pid)
437
487
  when :exit
438
488
  exit(130)
439
489
  when Proc
440
490
  @interrupt.call
441
491
  when :noop
442
- return
492
+ # Noop
443
493
  else
444
494
  raise InputInterrupt
445
495
  end