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