tty2-reader 0.9.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +120 -0
- data/lib/tty2/reader/completer.rb +188 -0
- data/lib/tty2/reader/completion_event.rb +36 -0
- data/lib/tty2/reader/completions.rb +107 -0
- data/lib/tty2/reader/console.rb +68 -0
- data/lib/tty2/reader/history.rb +184 -0
- data/lib/tty2/reader/key_event.rb +58 -0
- data/lib/tty2/reader/keys.rb +166 -0
- data/lib/tty2/reader/line.rb +367 -0
- data/lib/tty2/reader/mode.rb +42 -0
- data/lib/tty2/reader/version.rb +7 -0
- data/lib/tty2/reader/win_api.rb +51 -0
- data/lib/tty2/reader/win_console.rb +90 -0
- data/lib/tty2/reader.rb +632 -0
- data/lib/tty2-reader.rb +1 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a44587ade2b8c04804f0af10d8254767432ef48fbf44b846b6139b71ec2bcd66
|
4
|
+
data.tar.gz: 419ad00150ac5a27e006fd193483a1babdc7ccc81d5045e9e45586879803bb05
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2aefc8c61704b430c04380ffc70317d3770578736d5596120d5a0459c1f28fd7dfea83922fc6bf0625d87ab7989f20eb870cb3475b6b2460bde9a08ca1ae7d1d
|
7
|
+
data.tar.gz: df40cf0b823f887d94a569568121363738b655afd127b0e6e21d9f8e4f2c258a3b12af72f61f3894c8c1f4ce38a659e85e4ea27b6fd4546e440a46f8f8d1235c
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## [v0.9.0] - unreleased
|
4
|
+
|
5
|
+
### Added
|
6
|
+
* Added word completion
|
7
|
+
|
8
|
+
### Changed
|
9
|
+
* Forked TTY and reworked to TTY2
|
10
|
+
* Extended KeyEvent with Line object
|
11
|
+
|
12
|
+
### Fix
|
13
|
+
|
14
|
+
|
15
|
+
[v0.9.0]: https://github.com/zzyzwicz/tty2-reader/compare/v0.9.0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Piotr Murach (https://piotrmurach.com)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
# TTY2::Reader
|
3
|
+
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/tty2-reader.svg)][gem]
|
5
|
+
[![Build status](https://ci.appveyor.com/api/projects/status/cj4owy2vlty2q1ko?svg=true)][appveyor]
|
6
|
+
|
7
|
+
[gem]: http://badge.fury.io/rb/tty2-reader
|
8
|
+
[gh_actions_ci]: https://github.com/zzyzwicz/tty2-reader/actions?query=workflow%3ACI
|
9
|
+
|
10
|
+
> A tty-reader fork with the objective of adding a customized word completion mechanism.
|
11
|
+
|
12
|
+
**TTY2::Reader** intends to be an up to date clone of [TTY::Reader](https://github.com/piotrmurach/tty-reader), solely extending it with a customized word completion mechanism.
|
13
|
+
This page only covers the applied modifications.
|
14
|
+
|
15
|
+
|
16
|
+
## Modifications
|
17
|
+
|
18
|
+
* Renamed to TTY2::Reader
|
19
|
+
* Extended KeyEvent with Line object
|
20
|
+
* Added word completion
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem "tty2-reader"
|
28
|
+
```
|
29
|
+
|
30
|
+
And then execute:
|
31
|
+
|
32
|
+
$ bundle
|
33
|
+
|
34
|
+
Or install it yourself as:
|
35
|
+
|
36
|
+
$ gem install tty2-reader
|
37
|
+
|
38
|
+
* [1. API (only the modified parts)](#1-api)
|
39
|
+
* [1.1 on](#11-on)
|
40
|
+
* [2. Configuration (only the modified parts)](#2-configuration)
|
41
|
+
* [2.1 completion_handler](#21-completion_handler)
|
42
|
+
* [2.2 completion_suffix](#22-completion_suffix)
|
43
|
+
* [2.3 completion_cycling](#23-completion_cycling)
|
44
|
+
|
45
|
+
## API
|
46
|
+
|
47
|
+
### 1.1 on
|
48
|
+
You can register to listen on a key pressed events. This can be done by calling `on` with a event name(s):
|
49
|
+
```ruby
|
50
|
+
reader.on(:keypress) { |event| .... }
|
51
|
+
```
|
52
|
+
or listen for multiple events:
|
53
|
+
```ruby
|
54
|
+
reader.on(:keyctrl_x, :keyescape) { |event| ... }
|
55
|
+
```
|
56
|
+
The `KeyEvent` object is yielded to a block whenever a particular key event fires. The event responds to:
|
57
|
+
|
58
|
+
* `key` - key pressed
|
59
|
+
* `value` - value of the key pressed
|
60
|
+
* `line` - the `Line` object of the currently edited line, a new `Line` object with empty content otherwise
|
61
|
+
|
62
|
+
The `value` returns the actual key pressed and the `line` the content for the currently edited line or is empty.
|
63
|
+
|
64
|
+
The `key` is an object that responds to following messages:
|
65
|
+
* `name` - the name of the event such as :up, :down, letter or digit
|
66
|
+
* `meta` - true if event is non-standard key associated
|
67
|
+
* `shift` - true if shift has been pressed with the key
|
68
|
+
* `ctrl` - true if ctrl has been pressed with the key
|
69
|
+
For example, to add listen to vim like navigation keys, one would do the following:
|
70
|
+
```ruby
|
71
|
+
reader.on(:keypress) do |event|
|
72
|
+
if event.value == "j"
|
73
|
+
...
|
74
|
+
end
|
75
|
+
if event.value == "k"
|
76
|
+
...
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
You can subscribe to more than one event:
|
81
|
+
```ruby
|
82
|
+
reader.on(:keypress) { |event| ... }
|
83
|
+
.on(:keydown) { |event| ... }
|
84
|
+
```
|
85
|
+
|
86
|
+
## Configuration
|
87
|
+
|
88
|
+
### 2.1. `:completion_handler`
|
89
|
+
This option allows you to define possible completions. It accepts a `proc` with the word that is to be completed as a first, and a context in which the word is to be completed as a second argument. By default set to nil. To use this:
|
90
|
+
```ruby
|
91
|
+
reader = TTY2::Reader.new(completion_handler: ->(word, context) { ... })
|
92
|
+
```
|
93
|
+
|
94
|
+
### 2.2. `:completion_suffix`
|
95
|
+
This option allows you to add a suffix to completed words. By default, no suffix is added. To add a suffix:
|
96
|
+
```ruby
|
97
|
+
reader = TTY2::Reader.new(completion_suffix: " ")
|
98
|
+
```
|
99
|
+
|
100
|
+
### 2.3. `:completion_cycling`
|
101
|
+
This option controls cycling through completion suggestions. By default set to `true`, and can be disabled with:
|
102
|
+
```ruby
|
103
|
+
reader = TTY2::Reader.new(completion_cycling: false)
|
104
|
+
```
|
105
|
+
|
106
|
+
## Contributing
|
107
|
+
|
108
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zzyzwicz/tty2-reader.
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
113
|
+
|
114
|
+
## Code of Conduct
|
115
|
+
|
116
|
+
Everyone interacting in the TTY2::Reader project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/tty-reader/blob/master/CODE_OF_CONDUCT.md).
|
117
|
+
|
118
|
+
## Copyright
|
119
|
+
|
120
|
+
Copyright (c) 2021 zzyzwicz. See LICENSE for further details.
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "completions"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Reader
|
7
|
+
# Responsible for word completion
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Completer
|
11
|
+
# The completion suggestions
|
12
|
+
attr_reader :completions
|
13
|
+
|
14
|
+
# The handler for finding word completion suggestions
|
15
|
+
attr_accessor :handler
|
16
|
+
|
17
|
+
# The suffix to add to suggested word completion
|
18
|
+
attr_accessor :suffix
|
19
|
+
|
20
|
+
# Enable / Disable Cycling
|
21
|
+
attr_accessor :cycling
|
22
|
+
|
23
|
+
# The word to complete
|
24
|
+
attr_reader :word
|
25
|
+
|
26
|
+
# Create a Completer instance
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def initialize(handler: nil, suffix: "", cycling: true)
|
30
|
+
@handler = handler
|
31
|
+
@suffix = suffix
|
32
|
+
@cycling = cycling
|
33
|
+
@completions = Completions.new
|
34
|
+
@show_initial = false
|
35
|
+
@word = ""
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find a suggestion to complete a word
|
39
|
+
#
|
40
|
+
# @param [Line] line
|
41
|
+
# the line to complete a word in
|
42
|
+
# @param [Symbol] direction
|
43
|
+
# the direction in which to cycle through completions
|
44
|
+
# @param [Boolean] initial
|
45
|
+
# whether to find initial or next completion suggestion
|
46
|
+
#
|
47
|
+
# @return [String, nil]
|
48
|
+
# the completed word or nil when no suggestion is found
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def complete(line, direction: :next, initial: false)
|
52
|
+
if initial
|
53
|
+
complete_initial(line, direction: direction)
|
54
|
+
elsif @cycling
|
55
|
+
complete_next(line, direction: direction)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Complete the initial word
|
60
|
+
#
|
61
|
+
# @param [Line] line
|
62
|
+
# the line to complete a word in
|
63
|
+
# @param [Symbol] direction
|
64
|
+
# the direction in which to cycle through completions
|
65
|
+
#
|
66
|
+
# @return [String, nil]
|
67
|
+
# the completed word or nil when no suggestion is found
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def complete_initial(line, direction: :next)
|
71
|
+
completed_word = complete_word(line)
|
72
|
+
return if @completions.empty?
|
73
|
+
if @cycling && completions.size > 1
|
74
|
+
@word = line.word_to_complete
|
75
|
+
position = word.length
|
76
|
+
completions.previous if direction == :previous
|
77
|
+
completed_word = completions.get
|
78
|
+
line.insert(completed_word[position..-1])
|
79
|
+
end
|
80
|
+
completed_word
|
81
|
+
end
|
82
|
+
|
83
|
+
# Complete a word with the next suggestion from completions
|
84
|
+
#
|
85
|
+
# @param [Line] line
|
86
|
+
# the line to complete a word in
|
87
|
+
# @param [Symbol] direction
|
88
|
+
# the direction in which to cycle through completions
|
89
|
+
#
|
90
|
+
# @return [String, nil]
|
91
|
+
# the completed word or nil when no suggestion is found
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def complete_next(line, direction: :next)
|
95
|
+
return if completions.size < 2
|
96
|
+
|
97
|
+
previous_suggestion = completions.get
|
98
|
+
first_or_last = direction == :previous ? :first? : :last?
|
99
|
+
if completions.send(first_or_last) && !@show_initial
|
100
|
+
@show_initial = true
|
101
|
+
completed_word = word
|
102
|
+
else
|
103
|
+
if @show_initial
|
104
|
+
@show_initial = false
|
105
|
+
previous_suggestion = word
|
106
|
+
end
|
107
|
+
completions.send(direction)
|
108
|
+
completed_word = completions.get
|
109
|
+
end
|
110
|
+
|
111
|
+
length_to_remove = previous_suggestion.length
|
112
|
+
|
113
|
+
line.remove(length_to_remove)
|
114
|
+
#Due to a suspected timeing issue, verify all chars have actually been removed
|
115
|
+
if line.word_to_complete.length == 0
|
116
|
+
line.insert(completed_word)
|
117
|
+
completed_word
|
118
|
+
else
|
119
|
+
completions.previous
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get suggestions and populate completions
|
126
|
+
#
|
127
|
+
# @param [Line] line
|
128
|
+
# the line to complete a word in
|
129
|
+
#
|
130
|
+
# @api public
|
131
|
+
def load_completions(line)
|
132
|
+
@word = line.word_to_complete
|
133
|
+
context = line.subtext[0..-@word.length]
|
134
|
+
suggestions = handler.(word, context)
|
135
|
+
suggestions = suggestions.grep(/^#{Regexp.escape(@word)}/)
|
136
|
+
completions.clear
|
137
|
+
return if suggestions.empty?
|
138
|
+
completions.concat(suggestions)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Find suggestions and complete the current word as far as it is unambigous
|
142
|
+
#
|
143
|
+
# @param [Line] line
|
144
|
+
# the line to complete a word in
|
145
|
+
#
|
146
|
+
# @return [String]
|
147
|
+
# the completed word
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def complete_word(line)
|
151
|
+
load_completions(line)
|
152
|
+
|
153
|
+
return if completions.empty?
|
154
|
+
|
155
|
+
position = @word.length
|
156
|
+
if completions.size > 1
|
157
|
+
char = completions.first[position]
|
158
|
+
while completions.all? { |candidate| candidate[position] == char }
|
159
|
+
line.insert(char)
|
160
|
+
@word = line.word_to_complete
|
161
|
+
position = @word.length
|
162
|
+
char = completions.first[position]
|
163
|
+
end
|
164
|
+
completed_word = word
|
165
|
+
elsif completions.size == 1
|
166
|
+
completed_word = completions.first
|
167
|
+
line.insert(completions.first[position..-1] + suffix)
|
168
|
+
end
|
169
|
+
|
170
|
+
completed_word
|
171
|
+
end
|
172
|
+
|
173
|
+
# Cancel completion suggestion and revert to the initial word
|
174
|
+
#
|
175
|
+
# @param [Line] line
|
176
|
+
# the line to cancel word completion in
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def cancel(line)
|
180
|
+
return if completions.empty?
|
181
|
+
|
182
|
+
completed_word = @show_initial ? word : completions.get
|
183
|
+
line.remove(completed_word.length)
|
184
|
+
line.insert(word)
|
185
|
+
end
|
186
|
+
end # Completer
|
187
|
+
end # Reader
|
188
|
+
end # TTY2
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTY2
|
4
|
+
class Reader
|
5
|
+
class CompletionEvent
|
6
|
+
# The suggested word completion
|
7
|
+
attr_reader :completion
|
8
|
+
|
9
|
+
# The completion suggestions
|
10
|
+
attr_reader :completions
|
11
|
+
|
12
|
+
# The line with word to complete
|
13
|
+
attr_reader :line
|
14
|
+
|
15
|
+
# The initial word to complete
|
16
|
+
attr_reader :word
|
17
|
+
|
18
|
+
# Create a CompletionEvent
|
19
|
+
#
|
20
|
+
# @param [Completer] completer
|
21
|
+
# the word completer
|
22
|
+
# @param [String] completion
|
23
|
+
# the word completion
|
24
|
+
# @param [String] line
|
25
|
+
# the line with the word to complete
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def initialize(completer, completion, line)
|
29
|
+
@completion = completion
|
30
|
+
@completions = completer.completions.to_a
|
31
|
+
@line = line
|
32
|
+
@word = completer.word
|
33
|
+
end
|
34
|
+
end # CompletionEvent
|
35
|
+
end # Reader
|
36
|
+
end # TTY2
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module TTY2
|
6
|
+
class Reader
|
7
|
+
# Responsible for storing and navigating completion suggestions
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Completions
|
11
|
+
include Enumerable
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
def_delegators :@completions, :size, :empty?
|
15
|
+
|
16
|
+
# Create a Completions collection
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def initialize
|
20
|
+
@completions = []
|
21
|
+
@index = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
# Clear current completions
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def clear
|
28
|
+
@completions.clear
|
29
|
+
@index = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check whether the current index is at the first completion or not
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def first?
|
38
|
+
@index.zero?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Check whether the current index is at the last completion or not
|
42
|
+
#
|
43
|
+
# @return [Boolean]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
def last?
|
47
|
+
@index == size - 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add completion suggestions
|
51
|
+
#
|
52
|
+
# @param [Array<String>] suggestions
|
53
|
+
# the suggestions to add
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def concat(suggestions)
|
57
|
+
suggestions.each { |suggestion| @completions << suggestion.dup }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Iterate over all completions
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def each(&block)
|
64
|
+
if block_given?
|
65
|
+
@completions.each(&block)
|
66
|
+
else
|
67
|
+
@completions.to_enum
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieve completion at the current index
|
72
|
+
#
|
73
|
+
# @return [String]
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def get
|
77
|
+
@completions[@index]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Move index to the next completion
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def next
|
84
|
+
return if size.zero?
|
85
|
+
|
86
|
+
if @index == size - 1
|
87
|
+
@index = 0
|
88
|
+
else
|
89
|
+
@index += 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Move index to the previous completion
|
94
|
+
#
|
95
|
+
# @api public
|
96
|
+
def previous
|
97
|
+
return if size.zero?
|
98
|
+
|
99
|
+
if @index.zero?
|
100
|
+
@index = size - 1
|
101
|
+
else
|
102
|
+
@index -= 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end # Completions
|
106
|
+
end # Reader
|
107
|
+
end # TTY2
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "io/wait"
|
4
|
+
|
5
|
+
require_relative "keys"
|
6
|
+
require_relative "mode"
|
7
|
+
|
8
|
+
module TTY2
|
9
|
+
class Reader
|
10
|
+
class Console
|
11
|
+
ESC = "\e"
|
12
|
+
CSI = "\e["
|
13
|
+
|
14
|
+
TIMEOUT = 0.1
|
15
|
+
|
16
|
+
# Key codes
|
17
|
+
#
|
18
|
+
# @return [Hash[Symbol]]
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
attr_reader :keys
|
22
|
+
|
23
|
+
# Escape codes
|
24
|
+
#
|
25
|
+
# @return [Array[Integer]]
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
attr_reader :escape_codes
|
29
|
+
|
30
|
+
def initialize(input)
|
31
|
+
@input = input
|
32
|
+
@mode = Mode.new(input)
|
33
|
+
@keys = Keys.ctrl_keys.merge(Keys.keys)
|
34
|
+
@escape_codes = [[ESC.ord], CSI.bytes.to_a]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get a character from console with echo
|
38
|
+
#
|
39
|
+
# @param [Boolean] echo
|
40
|
+
# whether to echo input back or not, defaults to true
|
41
|
+
# @param [Boolean] raw
|
42
|
+
# whether to use raw mode or not, defaults to false
|
43
|
+
# @param [Boolean] nonblock
|
44
|
+
# whether to wait for input or not, defaults to false
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
#
|
48
|
+
# @api private
|
49
|
+
def get_char(echo: true, raw: false, nonblock: false)
|
50
|
+
mode.raw(raw) do
|
51
|
+
mode.echo(echo) do
|
52
|
+
if nonblock
|
53
|
+
input.wait_readable(TIMEOUT) ? input.getc : nil
|
54
|
+
else
|
55
|
+
input.getc
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
attr_reader :mode
|
64
|
+
|
65
|
+
attr_reader :input
|
66
|
+
end # Console
|
67
|
+
end # Reader
|
68
|
+
end # TTY2
|