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 +4 -4
- data/CHANGELOG.md +99 -0
- data/LICENSE.txt +1 -1
- data/README.md +75 -25
- data/lib/tty-reader.rb +0 -2
- data/lib/tty/reader.rb +134 -84
- data/lib/tty/reader/console.rb +23 -11
- data/lib/tty/reader/history.rb +39 -15
- data/lib/tty/reader/key_event.rb +4 -5
- data/lib/tty/reader/keys.rb +15 -16
- data/lib/tty/reader/line.rb +30 -17
- data/lib/tty/reader/mode.rb +1 -2
- data/lib/tty/reader/version.rb +2 -2
- data/lib/tty/reader/win_api.rb +2 -5
- data/lib/tty/reader/win_console.rb +18 -18
- metadata +35 -57
- data/Rakefile +0 -10
- data/bin/console +0 -6
- data/bin/setup +0 -8
- data/examples/keypress.rb +0 -16
- data/examples/line.rb +0 -7
- data/examples/multiline.rb +0 -7
- data/examples/noecho.rb +0 -6
- data/examples/shell.rb +0 -12
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-reader.gemspec +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7766384b749f39d5646ce4ff20d95c1bdfdaac5af096d4a996a7a347379874ee
|
4
|
+
data.tar.gz: c5a85b72fae90d018f79acdd9303c9926b75236fb67a2b24e641f6737a001372
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74bd44d949d086d8debe53bdd571755b263fce26d991dd7798d45fbb24513cdadea0a29c31e3388a65ce8f81f3b3514955d69c44c30af87465ba32933f5f6997
|
7
|
+
data.tar.gz: 4ad0c8d086506a2672f2a8bb8db1fbd9f951e2f1f9200f4c258e82967cf61bbea209a33fdd86aa4cfb99adf4e5b7fcdcf6e5b0f992da6fe9956daa5d5aade238
|
data/CHANGELOG.md
ADDED
@@ -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
|
data/LICENSE.txt
CHANGED
@@ -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]
|
2
6
|
|
3
7
|
[][gem]
|
4
|
-
[][gh_actions_ci]
|
5
9
|
[][appveyor]
|
6
10
|
[][codeclimate]
|
7
11
|
[][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.
|
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
|
+

|
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
|
-
*
|
32
|
-
*
|
33
|
-
* Reading multiline input
|
34
|
-
*
|
35
|
-
*
|
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
|
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
|
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
|
-
# >>
|
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
|
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
|
-
|
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 ==
|
211
|
+
if event.value == "j"
|
162
212
|
...
|
163
213
|
end
|
164
214
|
|
165
|
-
if event.value ==
|
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
|
-
|
175
|
-
.on(:keydown) { |
|
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
|
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
|
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 ==
|
278
|
+
if event.value == "j"
|
229
279
|
reader.trigger(:keydown)
|
230
280
|
end
|
231
|
-
if evevnt.value ==
|
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
|
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
|
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
|
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
|
402
|
+
Copyright (c) 2017 Piotr Murach. See LICENSE for further details.
|
data/lib/tty-reader.rb
CHANGED
data/lib/tty/reader.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "tty-cursor"
|
4
|
+
require "tty-screen"
|
5
|
+
require "wisper"
|
7
6
|
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
-
require_relative
|
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(
|
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 [
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
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(
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@
|
75
|
-
|
76
|
-
@
|
77
|
-
@
|
78
|
-
|
79
|
-
@
|
80
|
-
@
|
81
|
-
|
82
|
-
|
83
|
-
@
|
84
|
-
|
85
|
-
h.
|
86
|
-
h.
|
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[
|
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
|
160
|
-
#
|
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 [
|
165
|
-
# @option options [Boolean] echo
|
174
|
+
# @param [Boolean] echo
|
166
175
|
# whether to echo chars back or not, defaults to false
|
167
|
-
# @option
|
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(
|
174
|
-
|
175
|
-
|
176
|
-
|
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 [
|
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(
|
192
|
-
|
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(
|
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
|
-
#
|
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 =
|
224
|
-
|
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
|
253
|
+
output.print(line)
|
229
254
|
|
230
|
-
while (codes = get_codes(
|
231
|
-
|
232
|
-
|
255
|
+
while (codes = get_codes(echo: echo, raw: raw, nonblock: nonblock)) &&
|
256
|
+
(code = codes[0])
|
257
|
+
char = codes.pack("U*")
|
233
258
|
|
234
|
-
|
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
|
264
|
+
if raw && echo
|
237
265
|
clear_display(line, screen_width)
|
238
266
|
end
|
239
267
|
|
240
|
-
if console.keys[char] == :backspace ||
|
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 ||
|
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
|
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 ||
|
270
|
-
if
|
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? ?
|
302
|
+
output.print(?\s + (line.start? ? "" : ?\b))
|
274
303
|
end
|
275
304
|
end
|
276
305
|
|
277
|
-
|
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
|
-
|
319
|
+
buffer = ""
|
320
|
+
output.puts unless echo
|
288
321
|
break
|
289
322
|
end
|
290
323
|
end
|
291
|
-
|
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(
|
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(
|
355
|
-
|
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(
|
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
|
-
|
492
|
+
# Noop
|
443
493
|
else
|
444
494
|
raise InputInterrupt
|
445
495
|
end
|