tty-prompt 0.18.1 → 0.23.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 +95 -0
- data/README.md +598 -256
- data/lib/tty-prompt.rb +1 -2
- data/lib/tty/prompt.rb +192 -144
- data/lib/tty/prompt/answers_collector.rb +5 -5
- data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +20 -19
- data/lib/tty/prompt/choice.rb +9 -10
- data/lib/tty/prompt/choices.rb +30 -12
- data/lib/tty/prompt/confirm_question.rb +42 -16
- data/lib/tty/prompt/const.rb +17 -0
- data/lib/tty/prompt/converter_dsl.rb +6 -7
- data/lib/tty/prompt/converter_registry.rb +31 -26
- data/lib/tty/prompt/converters.rb +139 -32
- data/lib/tty/prompt/enum_list.rb +81 -26
- data/lib/tty/prompt/errors.rb +31 -0
- data/lib/tty/prompt/evaluator.rb +2 -2
- data/lib/tty/prompt/expander.rb +45 -15
- data/lib/tty/prompt/keypress.rb +33 -36
- data/lib/tty/prompt/list.rb +198 -63
- data/lib/tty/prompt/mask_question.rb +11 -8
- data/lib/tty/prompt/multi_list.rb +131 -28
- data/lib/tty/prompt/multiline.rb +9 -7
- data/lib/tty/prompt/paginator.rb +38 -26
- data/lib/tty/prompt/question.rb +92 -37
- data/lib/tty/prompt/question/checks.rb +20 -2
- data/lib/tty/prompt/question/modifier.rb +4 -2
- data/lib/tty/prompt/question/validation.rb +3 -3
- data/lib/tty/prompt/selected_choices.rb +77 -0
- data/lib/tty/prompt/slider.rb +125 -30
- data/lib/tty/prompt/statement.rb +3 -3
- data/lib/tty/prompt/suggestion.rb +7 -6
- data/lib/tty/prompt/symbols.rb +58 -34
- data/lib/tty/prompt/test.rb +36 -0
- data/lib/tty/prompt/timer.rb +75 -0
- data/lib/tty/prompt/utils.rb +1 -3
- data/lib/tty/prompt/version.rb +1 -1
- metadata +29 -227
- data/Rakefile +0 -8
- data/examples/ask.rb +0 -7
- data/examples/ask_valid.rb +0 -12
- data/examples/collect.rb +0 -21
- data/examples/echo.rb +0 -11
- data/examples/enum_select.rb +0 -7
- data/examples/enum_select_disabled.rb +0 -16
- data/examples/enum_select_paged.rb +0 -9
- data/examples/enum_select_wrapped.rb +0 -15
- data/examples/expand.rb +0 -29
- data/examples/in.rb +0 -9
- data/examples/inputs.rb +0 -10
- data/examples/key_events.rb +0 -15
- data/examples/keypress.rb +0 -9
- data/examples/mask.rb +0 -13
- data/examples/multi_select.rb +0 -8
- data/examples/multi_select_disabled.rb +0 -17
- data/examples/multi_select_paged.rb +0 -9
- data/examples/multi_select_wrapped.rb +0 -15
- data/examples/multiline.rb +0 -9
- data/examples/pause.rb +0 -9
- data/examples/select.rb +0 -24
- data/examples/select_disabled.rb +0 -18
- data/examples/select_enum.rb +0 -8
- data/examples/select_filtered.rb +0 -11
- data/examples/select_paginated.rb +0 -11
- data/examples/select_wrapped.rb +0 -15
- data/examples/slider.rb +0 -6
- data/examples/validation.rb +0 -9
- data/examples/yes_no.rb +0 -7
- data/lib/tty/prompt/messages.rb +0 -49
- data/lib/tty/prompt/timeout.rb +0 -78
- data/lib/tty/test_prompt.rb +0 -20
- data/spec/spec_helper.rb +0 -45
- data/spec/unit/ask_spec.rb +0 -132
- data/spec/unit/choice/eql_spec.rb +0 -22
- data/spec/unit/choice/from_spec.rb +0 -96
- data/spec/unit/choices/add_spec.rb +0 -12
- data/spec/unit/choices/each_spec.rb +0 -13
- data/spec/unit/choices/find_by_spec.rb +0 -10
- data/spec/unit/choices/new_spec.rb +0 -10
- data/spec/unit/choices/pluck_spec.rb +0 -9
- data/spec/unit/collect_spec.rb +0 -96
- data/spec/unit/converters/convert_bool_spec.rb +0 -58
- data/spec/unit/converters/convert_char_spec.rb +0 -11
- data/spec/unit/converters/convert_custom_spec.rb +0 -14
- data/spec/unit/converters/convert_date_spec.rb +0 -34
- data/spec/unit/converters/convert_file_spec.rb +0 -18
- data/spec/unit/converters/convert_number_spec.rb +0 -39
- data/spec/unit/converters/convert_path_spec.rb +0 -15
- data/spec/unit/converters/convert_range_spec.rb +0 -22
- data/spec/unit/converters/convert_regex_spec.rb +0 -12
- data/spec/unit/converters/convert_string_spec.rb +0 -21
- data/spec/unit/converters/on_error_spec.rb +0 -9
- data/spec/unit/distance/distance_spec.rb +0 -73
- data/spec/unit/enum_paginator_spec.rb +0 -75
- data/spec/unit/enum_select_spec.rb +0 -446
- data/spec/unit/error_spec.rb +0 -20
- data/spec/unit/evaluator_spec.rb +0 -67
- data/spec/unit/expand_spec.rb +0 -198
- data/spec/unit/keypress_spec.rb +0 -72
- data/spec/unit/mask_spec.rb +0 -132
- data/spec/unit/multi_select_spec.rb +0 -511
- data/spec/unit/multiline_spec.rb +0 -77
- data/spec/unit/new_spec.rb +0 -20
- data/spec/unit/ok_spec.rb +0 -10
- data/spec/unit/paginator_spec.rb +0 -73
- data/spec/unit/question/checks_spec.rb +0 -97
- data/spec/unit/question/default_spec.rb +0 -31
- data/spec/unit/question/echo_spec.rb +0 -38
- data/spec/unit/question/in_spec.rb +0 -115
- data/spec/unit/question/initialize_spec.rb +0 -12
- data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
- data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
- data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
- data/spec/unit/question/modify_spec.rb +0 -41
- data/spec/unit/question/required_spec.rb +0 -92
- data/spec/unit/question/validate_spec.rb +0 -115
- data/spec/unit/question/validation/call_spec.rb +0 -31
- data/spec/unit/question/validation/coerce_spec.rb +0 -30
- data/spec/unit/result_spec.rb +0 -40
- data/spec/unit/say_spec.rb +0 -67
- data/spec/unit/select_spec.rb +0 -660
- data/spec/unit/slider_spec.rb +0 -100
- data/spec/unit/statement/initialize_spec.rb +0 -15
- data/spec/unit/subscribe_spec.rb +0 -22
- data/spec/unit/suggest_spec.rb +0 -28
- data/spec/unit/warn_spec.rb +0 -21
- data/spec/unit/yes_no_spec.rb +0 -251
- data/tasks/console.rake +0 -11
- data/tasks/coverage.rake +0 -11
- data/tasks/spec.rake +0 -29
- data/tty-prompt.gemspec +0 -33
data/lib/tty/prompt/evaluator.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "result"
|
4
4
|
|
5
5
|
module TTY
|
6
6
|
class Prompt
|
@@ -23,7 +23,7 @@ module TTY
|
|
23
23
|
def check(proc = nil, &block)
|
24
24
|
results << (proc || block)
|
25
25
|
end
|
26
|
-
|
26
|
+
alias << check
|
27
27
|
end # Evaluator
|
28
28
|
end # Prompt
|
29
29
|
end # TTY
|
data/lib/tty/prompt/expander.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative "choices"
|
4
4
|
|
5
5
|
module TTY
|
6
6
|
class Prompt
|
@@ -10,20 +10,25 @@ module TTY
|
|
10
10
|
# @api private
|
11
11
|
class Expander
|
12
12
|
HELP_CHOICE = {
|
13
|
-
key:
|
14
|
-
name:
|
13
|
+
key: "h",
|
14
|
+
name: "print help",
|
15
15
|
value: :help
|
16
16
|
}.freeze
|
17
17
|
|
18
|
+
# Names for delete keys
|
19
|
+
DELETE_KEYS = %i[backspace delete].freeze
|
20
|
+
|
18
21
|
# Create instance of Expander
|
19
22
|
#
|
20
23
|
# @api public
|
21
24
|
def initialize(prompt, options = {})
|
22
25
|
@prompt = prompt
|
23
26
|
@prefix = options.fetch(:prefix) { @prompt.prefix }
|
24
|
-
@default = options.fetch(:default
|
27
|
+
@default = options.fetch(:default, 1)
|
28
|
+
@auto_hint = options.fetch(:auto_hint, false)
|
25
29
|
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
26
30
|
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
31
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
27
32
|
@choices = Choices.new
|
28
33
|
@selected = nil
|
29
34
|
@done = false
|
@@ -55,15 +60,16 @@ module TTY
|
|
55
60
|
|
56
61
|
selected = select_choice(@input)
|
57
62
|
|
58
|
-
if selected && selected.key.to_s ==
|
63
|
+
if selected && selected.key.to_s == "h"
|
59
64
|
expand
|
60
65
|
@selected = nil
|
61
|
-
@input =
|
66
|
+
@input = ""
|
62
67
|
elsif selected
|
63
68
|
@done = true
|
64
69
|
@selected = selected
|
70
|
+
@hint = nil
|
65
71
|
else
|
66
|
-
@input =
|
72
|
+
@input = ""
|
67
73
|
end
|
68
74
|
end
|
69
75
|
alias keyreturn keyenter
|
@@ -72,11 +78,12 @@ module TTY
|
|
72
78
|
#
|
73
79
|
# @api public
|
74
80
|
def keypress(event)
|
75
|
-
if
|
81
|
+
if DELETE_KEYS.include?(event.key.name)
|
76
82
|
@input.chop! unless @input.empty?
|
77
83
|
elsif event.value =~ /^[^\e\n\r]/
|
78
84
|
@input += event.value
|
79
85
|
end
|
86
|
+
|
80
87
|
@selected = select_choice(@input)
|
81
88
|
if @selected && !@default_key && collapsed?
|
82
89
|
@hint = @selected.name
|
@@ -97,9 +104,17 @@ module TTY
|
|
97
104
|
# @api public
|
98
105
|
def default(value = (not_set = true))
|
99
106
|
return @default if not_set
|
107
|
+
|
100
108
|
@default = value
|
101
109
|
end
|
102
110
|
|
111
|
+
# Set quiet mode.
|
112
|
+
#
|
113
|
+
# @api public
|
114
|
+
def quiet(value)
|
115
|
+
@quiet = value
|
116
|
+
end
|
117
|
+
|
103
118
|
# Add a single choice
|
104
119
|
#
|
105
120
|
# @api public
|
@@ -151,19 +166,19 @@ module TTY
|
|
151
166
|
elsif @input.to_s.empty? && default_key
|
152
167
|
keys[@default - 1] = @prompt.decorate(default_key, @active_color)
|
153
168
|
end
|
154
|
-
keys.join(
|
169
|
+
keys.join(",")
|
155
170
|
end
|
156
171
|
|
157
172
|
# @api private
|
158
173
|
def render
|
159
|
-
@input =
|
174
|
+
@input = ""
|
160
175
|
until @done
|
161
176
|
question = render_question
|
162
177
|
@prompt.print(question)
|
163
178
|
read_input
|
164
179
|
@prompt.print(refresh(question.lines.count))
|
165
180
|
end
|
166
|
-
@prompt.print(render_question)
|
181
|
+
@prompt.print(render_question) unless @quiet
|
167
182
|
answer
|
168
183
|
end
|
169
184
|
|
@@ -196,7 +211,7 @@ module TTY
|
|
196
211
|
#
|
197
212
|
# @api private
|
198
213
|
def render_hint
|
199
|
-
"\n" + @prompt.decorate(
|
214
|
+
"\n" + @prompt.decorate(">> ", @active_color) +
|
200
215
|
@hint +
|
201
216
|
@prompt.cursor.prev_line +
|
202
217
|
@prompt.cursor.forward(@prompt.strip(render_header).size)
|
@@ -208,6 +223,7 @@ module TTY
|
|
208
223
|
#
|
209
224
|
# @api private
|
210
225
|
def render_question
|
226
|
+
load_auto_hint if @auto_hint
|
211
227
|
header = render_header
|
212
228
|
header << render_hint if @hint
|
213
229
|
header << "\n" if @done
|
@@ -219,6 +235,20 @@ module TTY
|
|
219
235
|
header
|
220
236
|
end
|
221
237
|
|
238
|
+
def load_auto_hint
|
239
|
+
if @hint.nil? && collapsed?
|
240
|
+
if @selected
|
241
|
+
@hint = @selected.name
|
242
|
+
else
|
243
|
+
if @input.empty?
|
244
|
+
@hint = @choices[@default - 1].name
|
245
|
+
else
|
246
|
+
@hint = "invalid option"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
222
252
|
def render_footer
|
223
253
|
" Choice [#{@choices[@default - 1].key}]: #{@input}"
|
224
254
|
end
|
@@ -235,7 +265,7 @@ module TTY
|
|
235
265
|
#
|
236
266
|
# @api private
|
237
267
|
def refresh(lines)
|
238
|
-
if @hint && (!@selected || @done)
|
268
|
+
if (@hint && (!@selected || @done)) || (@auto_hint && collapsed?)
|
239
269
|
@hint = nil
|
240
270
|
@prompt.clear_lines(lines, :down) +
|
241
271
|
@prompt.cursor.prev_line
|
@@ -256,7 +286,7 @@ module TTY
|
|
256
286
|
if @selected && @selected.key == choice.key
|
257
287
|
chosen = @prompt.decorate(chosen, @active_color)
|
258
288
|
end
|
259
|
-
output <<
|
289
|
+
output << " " + chosen + "\n"
|
260
290
|
end
|
261
291
|
output.join
|
262
292
|
end
|
@@ -276,7 +306,7 @@ module TTY
|
|
276
306
|
if choice.key.length != 1
|
277
307
|
errors << "Choice key `#{choice.key}` is more than one character long."
|
278
308
|
end
|
279
|
-
if choice.key.to_s ==
|
309
|
+
if choice.key.to_s == "h"
|
280
310
|
errors << "Choice key `#{choice.key}` is reserved for help menu."
|
281
311
|
end
|
282
312
|
if keys.include?(choice.key)
|
data/lib/tty/prompt/keypress.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative 'timeout'
|
3
|
+
require_relative "question"
|
4
|
+
require_relative "timer"
|
6
5
|
|
7
6
|
module TTY
|
8
7
|
class Prompt
|
@@ -13,7 +12,7 @@ module TTY
|
|
13
12
|
# @param [Hash] options
|
14
13
|
#
|
15
14
|
# @api public
|
16
|
-
def initialize(prompt, options
|
15
|
+
def initialize(prompt, **options)
|
17
16
|
super
|
18
17
|
@echo = options.fetch(:echo) { false }
|
19
18
|
@keys = options.fetch(:keys) { UndefinedSetting }
|
@@ -21,24 +20,17 @@ module TTY
|
|
21
20
|
@interval = options.fetch(:interval) {
|
22
21
|
(@timeout != UndefinedSetting && @timeout < 1) ? @timeout : 1
|
23
22
|
}
|
23
|
+
@decimals = (@interval.to_s.split(".")[1] || []).size
|
24
24
|
@countdown = @timeout
|
25
|
-
|
26
|
-
|
27
|
-
question = render_question
|
28
|
-
line_size = question.size
|
29
|
-
total_lines = @prompt.count_screen_lines(line_size)
|
30
|
-
@prompt.print(refresh(question.lines.count, total_lines))
|
31
|
-
countdown(time)
|
32
|
-
@prompt.print(render_question)
|
33
|
-
end
|
34
|
-
}
|
35
|
-
@scheduler = Timeout.new(interval_handler: @interval_handler)
|
25
|
+
time = timeout? ? Float(@timeout) : nil
|
26
|
+
@timer = Timer.new(time, Float(@interval))
|
36
27
|
|
37
28
|
@prompt.subscribe(self)
|
38
29
|
end
|
39
30
|
|
40
31
|
def countdown(value = (not_set = true))
|
41
32
|
return @countdown if not_set
|
33
|
+
|
42
34
|
@countdown = value
|
43
35
|
end
|
44
36
|
|
@@ -55,10 +47,8 @@ module TTY
|
|
55
47
|
def keypress(event)
|
56
48
|
if any_key?
|
57
49
|
@done = true
|
58
|
-
@scheduler.cancel
|
59
50
|
elsif @keys.is_a?(Array) && @keys.include?(event.key.name)
|
60
51
|
@done = true
|
61
|
-
@scheduler.cancel
|
62
52
|
else
|
63
53
|
@done = false
|
64
54
|
end
|
@@ -66,36 +56,43 @@ module TTY
|
|
66
56
|
|
67
57
|
def render_question
|
68
58
|
header = super
|
69
|
-
|
59
|
+
if timeout?
|
60
|
+
header.gsub!(/:countdown/, format("%.#{@decimals}f", countdown))
|
61
|
+
end
|
70
62
|
header
|
71
63
|
end
|
72
64
|
|
65
|
+
def interval_handler(time)
|
66
|
+
return if @done
|
67
|
+
|
68
|
+
question = render_question
|
69
|
+
line_size = question.size
|
70
|
+
total_lines = @prompt.count_screen_lines(line_size)
|
71
|
+
@prompt.print(refresh(question.lines.count, total_lines))
|
72
|
+
countdown(time)
|
73
|
+
@prompt.print(render_question)
|
74
|
+
end
|
75
|
+
|
73
76
|
def process_input(question)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
@prompt.print(render_question)
|
78
|
+
|
79
|
+
@timer.on_tick do |time|
|
80
|
+
interval_handler(time)
|
81
|
+
end
|
82
|
+
|
83
|
+
@timer.while_remaining do |remaining|
|
84
|
+
break if @done
|
85
|
+
|
86
|
+
@input = @prompt.read_keypress(nonblock: true)
|
79
87
|
end
|
88
|
+
countdown(0) unless @done
|
89
|
+
|
80
90
|
@evaluator.(@input)
|
81
91
|
end
|
82
92
|
|
83
93
|
def refresh(lines, lines_to_clear)
|
84
94
|
@prompt.clear_lines(lines)
|
85
95
|
end
|
86
|
-
|
87
|
-
# Wait for keypress or timeout
|
88
|
-
#
|
89
|
-
# @api private
|
90
|
-
def time(&job)
|
91
|
-
if timeout?
|
92
|
-
time = Float(@timeout)
|
93
|
-
interval = Float(@interval)
|
94
|
-
@scheduler.timeout(time, interval, &job)
|
95
|
-
else
|
96
|
-
job.()
|
97
|
-
end
|
98
|
-
end
|
99
96
|
end # Keypress
|
100
97
|
end # Prompt
|
101
98
|
end # TTY
|
data/lib/tty/prompt/list.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "English"
|
4
4
|
|
5
|
-
require_relative
|
6
|
-
require_relative
|
7
|
-
require_relative
|
5
|
+
require_relative "choices"
|
6
|
+
require_relative "paginator"
|
7
|
+
require_relative "block_paginator"
|
8
8
|
|
9
9
|
module TTY
|
10
10
|
class Prompt
|
@@ -13,15 +13,12 @@ module TTY
|
|
13
13
|
#
|
14
14
|
# @api private
|
15
15
|
class List
|
16
|
-
include Symbols
|
17
|
-
|
18
|
-
HELP = '(Use arrow%s keys, press Enter to select%s)'
|
19
|
-
|
20
|
-
PAGE_HELP = '(Move up or down to reveal more choices)'
|
21
|
-
|
22
16
|
# Allowed keys for filter, along with backspace and canc.
|
23
17
|
FILTER_KEYS_MATCHER = /\A([[:alnum:]]|[[:punct:]])\Z/.freeze
|
24
18
|
|
19
|
+
# Checks type of default parameter to be integer
|
20
|
+
INTEGER_MATCHER = /\A\d+\Z/.freeze
|
21
|
+
|
25
22
|
# Create instance of TTY::Prompt::List menu.
|
26
23
|
#
|
27
24
|
# @param Hash options
|
@@ -36,7 +33,7 @@ module TTY
|
|
36
33
|
# the delimiter for the item index
|
37
34
|
#
|
38
35
|
# @api public
|
39
|
-
def initialize(prompt, options
|
36
|
+
def initialize(prompt, **options)
|
40
37
|
check_options_consistency(options)
|
41
38
|
|
42
39
|
@prompt = prompt
|
@@ -46,23 +43,33 @@ module TTY
|
|
46
43
|
@choices = Choices.new
|
47
44
|
@active_color = options.fetch(:active_color) { @prompt.active_color }
|
48
45
|
@help_color = options.fetch(:help_color) { @prompt.help_color }
|
49
|
-
@marker = options.fetch(:marker) { symbols[:pointer] }
|
50
46
|
@cycle = options.fetch(:cycle) { false }
|
51
47
|
@filterable = options.fetch(:filter) { false }
|
48
|
+
@symbols = @prompt.symbols.merge(options.fetch(:symbols, {}))
|
49
|
+
@quiet = options.fetch(:quiet) { @prompt.quiet }
|
52
50
|
@filter = []
|
51
|
+
@filter_cache = {}
|
53
52
|
@help = options[:help]
|
53
|
+
@show_help = options.fetch(:show_help) { :start }
|
54
54
|
@first_render = true
|
55
55
|
@done = false
|
56
56
|
@per_page = options[:per_page]
|
57
|
-
@page_help = options[:page_help] || PAGE_HELP
|
58
57
|
@paginator = Paginator.new
|
58
|
+
@block_paginator = BlockPaginator.new
|
59
|
+
@by_page = false
|
60
|
+
@paging_changed = false
|
59
61
|
end
|
60
62
|
|
61
|
-
#
|
63
|
+
# Change symbols used by this prompt
|
64
|
+
#
|
65
|
+
# @param [Hash] new_symbols
|
66
|
+
# the new symbols to use
|
62
67
|
#
|
63
68
|
# @api public
|
64
|
-
def
|
65
|
-
@
|
69
|
+
def symbols(new_symbols = (not_set = true))
|
70
|
+
return @symbols if not_set
|
71
|
+
|
72
|
+
@symbols.merge!(new_symbols)
|
66
73
|
end
|
67
74
|
|
68
75
|
# Set default option selected
|
@@ -72,6 +79,32 @@ module TTY
|
|
72
79
|
@default = default_values
|
73
80
|
end
|
74
81
|
|
82
|
+
# Select paginator based on the current navigation key
|
83
|
+
#
|
84
|
+
# @return [Paginator]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def paginator
|
88
|
+
@by_page ? @block_paginator : @paginator
|
89
|
+
end
|
90
|
+
|
91
|
+
# Synchronize paginators start positions
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
def sync_paginators
|
95
|
+
if @by_page
|
96
|
+
if @paginator.start_index
|
97
|
+
@block_paginator.reset!
|
98
|
+
@block_paginator.start_index = @paginator.start_index
|
99
|
+
end
|
100
|
+
else
|
101
|
+
if @block_paginator.start_index
|
102
|
+
@paginator.reset!
|
103
|
+
@paginator.start_index = @block_paginator.start_index
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
75
108
|
# Set number of items per page
|
76
109
|
#
|
77
110
|
# @api public
|
@@ -92,13 +125,6 @@ module TTY
|
|
92
125
|
choices.size > page_size
|
93
126
|
end
|
94
127
|
|
95
|
-
# @param [String] text
|
96
|
-
# the help text to display per page
|
97
|
-
# @api pbulic
|
98
|
-
def page_help(text)
|
99
|
-
@page_help = text
|
100
|
-
end
|
101
|
-
|
102
128
|
# Provide help information
|
103
129
|
#
|
104
130
|
# @param [String] value
|
@@ -113,20 +139,46 @@ module TTY
|
|
113
139
|
@help = (@help.nil? && !not_set) ? value : default_help
|
114
140
|
end
|
115
141
|
|
116
|
-
#
|
142
|
+
# Change when help is displayed
|
117
143
|
#
|
118
144
|
# @api public
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
145
|
+
def show_help(value = (not_set = true))
|
146
|
+
return @show_ehlp if not_set
|
147
|
+
|
148
|
+
@show_help = value
|
149
|
+
end
|
150
|
+
|
151
|
+
# Information about arrow keys
|
152
|
+
#
|
153
|
+
# @return [String]
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
def arrows_help
|
157
|
+
up_down = @symbols[:arrow_up] + "/" + @symbols[:arrow_down]
|
158
|
+
left_right = @symbols[:arrow_left] + "/" + @symbols[:arrow_right]
|
128
159
|
|
129
|
-
|
160
|
+
arrows = [up_down]
|
161
|
+
arrows << "/" if paginated?
|
162
|
+
arrows << left_right if paginated?
|
163
|
+
arrows.join
|
164
|
+
end
|
165
|
+
|
166
|
+
# Default help text
|
167
|
+
#
|
168
|
+
# Note that enumeration and filter are mutually exclusive
|
169
|
+
#
|
170
|
+
# @a public
|
171
|
+
def default_help
|
172
|
+
str = []
|
173
|
+
str << "(Press "
|
174
|
+
str << "#{arrows_help} arrow"
|
175
|
+
str << " or 1-#{choices.size} number" if enumerate?
|
176
|
+
str << " to move"
|
177
|
+
str << (filterable? ? "," : " and")
|
178
|
+
str << " Enter to select"
|
179
|
+
str << " and letters to filter" if filterable?
|
180
|
+
str << ")"
|
181
|
+
str.join
|
130
182
|
end
|
131
183
|
|
132
184
|
# Set selecting active index using number pad
|
@@ -136,10 +188,18 @@ module TTY
|
|
136
188
|
@enum = value
|
137
189
|
end
|
138
190
|
|
191
|
+
# Set whether selected answers are echoed
|
192
|
+
#
|
193
|
+
# @api public
|
194
|
+
def quiet(value)
|
195
|
+
@quiet = value
|
196
|
+
end
|
197
|
+
|
139
198
|
# Add a single choice
|
140
199
|
#
|
141
200
|
# @api public
|
142
201
|
def choice(*value, &block)
|
202
|
+
@filter_cache = {}
|
143
203
|
if block
|
144
204
|
@choices << (value << block)
|
145
205
|
else
|
@@ -159,12 +219,13 @@ module TTY
|
|
159
219
|
if !filterable? || @filter.empty?
|
160
220
|
@choices
|
161
221
|
else
|
162
|
-
@
|
163
|
-
|
164
|
-
|
222
|
+
filter_value = @filter.join.downcase
|
223
|
+
@filter_cache[filter_value] ||= @choices.enabled.select do |choice|
|
224
|
+
choice.name.to_s.downcase.include?(filter_value)
|
165
225
|
end
|
166
226
|
end
|
167
227
|
else
|
228
|
+
@filter_cache = {}
|
168
229
|
values.each { |val| @choices << val }
|
169
230
|
end
|
170
231
|
end
|
@@ -198,6 +259,7 @@ module TTY
|
|
198
259
|
value = event.value.to_i
|
199
260
|
return unless (1..choices.count).cover?(value)
|
200
261
|
return if choices[value - 1].disabled?
|
262
|
+
|
201
263
|
@active = value
|
202
264
|
end
|
203
265
|
|
@@ -223,6 +285,9 @@ module TTY
|
|
223
285
|
|
224
286
|
@active = prev_active if prev_active
|
225
287
|
end
|
288
|
+
|
289
|
+
@paging_changed = @by_page
|
290
|
+
@by_page = false
|
226
291
|
end
|
227
292
|
|
228
293
|
def keydown(*)
|
@@ -237,9 +302,52 @@ module TTY
|
|
237
302
|
|
238
303
|
@active = next_active if next_active
|
239
304
|
end
|
305
|
+
@paging_changed = @by_page
|
306
|
+
@by_page = false
|
240
307
|
end
|
241
308
|
alias keytab keydown
|
242
309
|
|
310
|
+
# Moves all choices page by page keeping the current selected item
|
311
|
+
# at the same level on each page.
|
312
|
+
#
|
313
|
+
# When the choice on a page is outside of next page range then
|
314
|
+
# adjust it to the last item, otherwise leave unchanged.
|
315
|
+
def keyright(*)
|
316
|
+
choices_size = choices.size
|
317
|
+
if (@active + page_size) <= choices_size
|
318
|
+
searchable = ((@active + page_size)..choices_size)
|
319
|
+
@active = search_choice_in(searchable)
|
320
|
+
elsif @active <= choices_size # last page shorter
|
321
|
+
current = @active % page_size
|
322
|
+
remaining = choices_size % page_size
|
323
|
+
|
324
|
+
if current.zero? || (remaining > 0 && current > remaining)
|
325
|
+
searchable = choices_size.downto(0).to_a
|
326
|
+
@active = search_choice_in(searchable)
|
327
|
+
elsif @cycle
|
328
|
+
searchable = ((current.zero? ? page_size : current)..choices_size)
|
329
|
+
@active = search_choice_in(searchable)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
@paging_changed = !@by_page
|
334
|
+
@by_page = true
|
335
|
+
end
|
336
|
+
alias keypage_down keyright
|
337
|
+
|
338
|
+
def keyleft(*)
|
339
|
+
if (@active - page_size) > 0
|
340
|
+
searchable = ((@active - page_size)..choices.size)
|
341
|
+
@active = search_choice_in(searchable)
|
342
|
+
elsif @cycle
|
343
|
+
searchable = choices.size.downto(1).to_a
|
344
|
+
@active = search_choice_in(searchable)
|
345
|
+
end
|
346
|
+
@paging_changed = !@by_page
|
347
|
+
@by_page = true
|
348
|
+
end
|
349
|
+
alias keypage_up keyleft
|
350
|
+
|
243
351
|
def keypress(event)
|
244
352
|
return unless filterable?
|
245
353
|
|
@@ -274,14 +382,19 @@ module TTY
|
|
274
382
|
|
275
383
|
# Setup default option and active selection
|
276
384
|
#
|
385
|
+
# @return [Integer]
|
386
|
+
#
|
277
387
|
# @api private
|
278
388
|
def setup_defaults
|
279
389
|
validate_defaults
|
280
390
|
|
281
|
-
if
|
391
|
+
if @default.empty?
|
392
|
+
# no default, pick the first non-disabled choice
|
393
|
+
@active = choices.index { |choice| !choice.disabled? } + 1
|
394
|
+
elsif @default.first.to_s =~ INTEGER_MATCHER
|
282
395
|
@active = @default.first
|
283
|
-
|
284
|
-
@active =
|
396
|
+
elsif default_choice = choices.find_by(:name, @default.first)
|
397
|
+
@active = choices.index(default_choice) + 1
|
285
398
|
end
|
286
399
|
end
|
287
400
|
|
@@ -296,16 +409,35 @@ module TTY
|
|
296
409
|
@default.each do |d|
|
297
410
|
msg = if d.nil? || d.to_s.empty?
|
298
411
|
"default index must be an integer in range (1 - #{choices.size})"
|
412
|
+
elsif d.to_s !~ INTEGER_MATCHER
|
413
|
+
validate_default_name(d)
|
299
414
|
elsif d < 1 || d > choices.size
|
300
415
|
"default index `#{d}` out of range (1 - #{choices.size})"
|
301
|
-
elsif choices[d - 1] &&
|
302
|
-
"default index `#{d}` matches disabled choice
|
416
|
+
elsif (dflt_choice = choices[d - 1]) && dflt_choice.disabled?
|
417
|
+
"default index `#{d}` matches disabled choice"
|
303
418
|
end
|
304
419
|
|
305
420
|
raise(ConfigurationError, msg) if msg
|
306
421
|
end
|
307
422
|
end
|
308
423
|
|
424
|
+
# Validate default choice name
|
425
|
+
#
|
426
|
+
# @param [String] name
|
427
|
+
# the name to verify
|
428
|
+
#
|
429
|
+
# @return [String]
|
430
|
+
#
|
431
|
+
# @api private
|
432
|
+
def validate_default_name(name)
|
433
|
+
default_choice = choices.find_by(:name, name.to_s)
|
434
|
+
if default_choice.nil?
|
435
|
+
"no choice found for the default name: #{name.inspect}"
|
436
|
+
elsif default_choice.disabled?
|
437
|
+
"default name #{name.inspect} matches disabled choice"
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
309
441
|
# Render a selection list.
|
310
442
|
#
|
311
443
|
# By default the result is printed out.
|
@@ -327,7 +459,7 @@ module TTY
|
|
327
459
|
|
328
460
|
@prompt.print(refresh(question_lines_count(question_lines)))
|
329
461
|
end
|
330
|
-
@prompt.print(render_question)
|
462
|
+
@prompt.print(render_question) unless @quiet
|
331
463
|
answer
|
332
464
|
ensure
|
333
465
|
@prompt.print(@prompt.show)
|
@@ -372,7 +504,6 @@ module TTY
|
|
372
504
|
@first_render = false
|
373
505
|
unless @done
|
374
506
|
header << render_menu
|
375
|
-
header << render_footer
|
376
507
|
end
|
377
508
|
header.join
|
378
509
|
end
|
@@ -395,6 +526,20 @@ module TTY
|
|
395
526
|
"(Filter: #{@filter.join.inspect})"
|
396
527
|
end
|
397
528
|
|
529
|
+
# Check if help is shown only on start
|
530
|
+
#
|
531
|
+
# @api private
|
532
|
+
def help_start?
|
533
|
+
@show_help =~ /start/i
|
534
|
+
end
|
535
|
+
|
536
|
+
# Check if help is always displayed
|
537
|
+
#
|
538
|
+
# @api private
|
539
|
+
def help_always?
|
540
|
+
@show_help =~ /always/i
|
541
|
+
end
|
542
|
+
|
398
543
|
# Render initial help and selected choice
|
399
544
|
#
|
400
545
|
# @return [String]
|
@@ -404,7 +549,8 @@ module TTY
|
|
404
549
|
if @done
|
405
550
|
selected_item = choices[@active - 1].name
|
406
551
|
@prompt.decorate(selected_item.to_s, @active_color)
|
407
|
-
elsif @first_render
|
552
|
+
elsif (@first_render && (help_start? || help_always?)) ||
|
553
|
+
(help_always? && !@filter.any?)
|
408
554
|
@prompt.decorate(help, @help_color)
|
409
555
|
elsif filterable? && @filter.any?
|
410
556
|
@prompt.decorate(filter_help, @help_color)
|
@@ -419,36 +565,25 @@ module TTY
|
|
419
565
|
def render_menu
|
420
566
|
output = []
|
421
567
|
|
422
|
-
|
423
|
-
|
568
|
+
sync_paginators if @paging_changed
|
569
|
+
paginator.paginate(choices, @active, @per_page) do |choice, index|
|
570
|
+
num = enumerate? ? (index + 1).to_s + @enum + " " : ""
|
424
571
|
message = if index + 1 == @active && !choice.disabled?
|
425
|
-
selected = "#{@marker} #{num}#{choice.name}"
|
572
|
+
selected = "#{@symbols[:marker]} #{num}#{choice.name}"
|
426
573
|
@prompt.decorate(selected.to_s, @active_color)
|
427
574
|
elsif choice.disabled?
|
428
|
-
@prompt.decorate(symbols[:cross], :red) +
|
575
|
+
@prompt.decorate(@symbols[:cross], :red) +
|
429
576
|
" #{num}#{choice.name} #{choice.disabled}"
|
430
577
|
else
|
431
578
|
" #{num}#{choice.name}"
|
432
579
|
end
|
433
|
-
|
434
|
-
newline = (index ==
|
580
|
+
end_index = paginated? ? paginator.end_index : choices.size - 1
|
581
|
+
newline = (index == end_index) ? "" : "\n"
|
435
582
|
output << (message + newline)
|
436
583
|
end
|
437
584
|
|
438
585
|
output.join
|
439
586
|
end
|
440
|
-
|
441
|
-
# Render page info footer
|
442
|
-
#
|
443
|
-
# @return [String]
|
444
|
-
#
|
445
|
-
# @api private
|
446
|
-
def render_footer
|
447
|
-
return '' unless paginated?
|
448
|
-
|
449
|
-
colored_footer = @prompt.decorate(@page_help, @help_color)
|
450
|
-
"\n" + colored_footer
|
451
|
-
end
|
452
587
|
end # List
|
453
588
|
end # Prompt
|
454
589
|
end # TTY
|