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