tty2-reader 0.9.0.1
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 +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]
|
5
|
+
[][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
|