tty-prompt 0.3.0 → 0.4.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/.travis.yml +4 -1
- data/CHANGELOG.md +15 -0
- data/Gemfile +2 -2
- data/README.md +185 -25
- data/examples/enum.rb +8 -0
- data/examples/enum_select.rb +7 -0
- data/examples/in.rb +3 -1
- data/examples/slider.rb +6 -0
- data/lib/tty-prompt.rb +8 -2
- data/lib/tty/prompt.rb +63 -13
- data/lib/tty/prompt/converters.rb +12 -6
- data/lib/tty/prompt/enum_list.rb +222 -0
- data/lib/tty/prompt/list.rb +48 -15
- data/lib/tty/prompt/multi_list.rb +11 -11
- data/lib/tty/prompt/question.rb +38 -14
- data/lib/tty/prompt/question/checks.rb +5 -3
- data/lib/tty/prompt/reader.rb +12 -18
- data/lib/tty/prompt/reader/codes.rb +15 -9
- data/lib/tty/prompt/reader/key_event.rb +51 -24
- data/lib/tty/prompt/slider.rb +170 -0
- data/lib/tty/prompt/symbols.rb +7 -1
- data/lib/tty/prompt/utils.rb +31 -3
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/converters/convert_bool_spec.rb +1 -1
- data/spec/unit/converters/convert_date_spec.rb +11 -2
- data/spec/unit/converters/convert_file_spec.rb +1 -1
- data/spec/unit/converters/convert_number_spec.rb +19 -2
- data/spec/unit/converters/convert_path_spec.rb +1 -1
- data/spec/unit/converters/convert_range_spec.rb +4 -3
- data/spec/unit/enum_select_spec.rb +93 -0
- data/spec/unit/multi_select_spec.rb +14 -12
- data/spec/unit/question/checks_spec.rb +97 -0
- data/spec/unit/reader/key_event_spec.rb +67 -0
- data/spec/unit/select_spec.rb +15 -16
- data/spec/unit/slider_spec.rb +54 -0
- data/tty-prompt.gemspec +2 -1
- metadata +31 -5
- data/.ruby-version +0 -1
@@ -9,8 +9,7 @@ module TTY
|
|
9
9
|
#
|
10
10
|
# @api private
|
11
11
|
class MultiList < List
|
12
|
-
|
13
|
-
HELP = '(Use arrow keys, press Space to select and Enter to finish)'.freeze
|
12
|
+
HELP = '(Use arrow%s keys, press Space to select and Enter to finish)'.freeze
|
14
13
|
|
15
14
|
# Create instance of TTY::Prompt::MultiList menu.
|
16
15
|
#
|
@@ -21,14 +20,14 @@ module TTY
|
|
21
20
|
def initialize(prompt, options)
|
22
21
|
super
|
23
22
|
@selected = []
|
24
|
-
@help
|
25
|
-
@default
|
23
|
+
@help = options[:help]
|
24
|
+
@default = options.fetch(:default) { [] }
|
26
25
|
end
|
27
26
|
|
28
27
|
# Callback fired when space key is pressed
|
29
28
|
#
|
30
29
|
# @api private
|
31
|
-
def keyspace(
|
30
|
+
def keyspace(*)
|
32
31
|
active_choice = @choices[@active - 1]
|
33
32
|
if @selected.include?(active_choice)
|
34
33
|
@selected.delete(active_choice)
|
@@ -57,9 +56,7 @@ module TTY
|
|
57
56
|
elsif @selected.size.nonzero?
|
58
57
|
@selected.map(&:name).join(', ')
|
59
58
|
elsif @first_render
|
60
|
-
@prompt.decorate(
|
61
|
-
else
|
62
|
-
''
|
59
|
+
@prompt.decorate(help, :bright_black)
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
@@ -76,18 +73,21 @@ module TTY
|
|
76
73
|
#
|
77
74
|
# @api private
|
78
75
|
def render_menu
|
76
|
+
output = ''
|
79
77
|
@choices.each_with_index do |choice, index|
|
78
|
+
num = enumerate? ? (index + 1).to_s + @enum + Symbols::SPACE : ''
|
80
79
|
indicator = (index + 1 == @active) ? @marker : Symbols::SPACE
|
81
80
|
indicator += Symbols::SPACE
|
82
81
|
message = if @selected.include?(choice)
|
83
82
|
selected = @prompt.decorate(Symbols::RADIO_CHECKED, :green)
|
84
|
-
selected + Symbols::SPACE + choice.name
|
83
|
+
selected + Symbols::SPACE + num + choice.name
|
85
84
|
else
|
86
|
-
Symbols::RADIO_UNCHECKED + Symbols::SPACE + choice.name
|
85
|
+
Symbols::RADIO_UNCHECKED + Symbols::SPACE + num + choice.name
|
87
86
|
end
|
88
87
|
newline = (index == @choices.length - 1) ? '' : "\n"
|
89
|
-
|
88
|
+
output << indicator + message + newline
|
90
89
|
end
|
90
|
+
output
|
91
91
|
end
|
92
92
|
end # MultiList
|
93
93
|
end # Prompt
|
data/lib/tty/prompt/question.rb
CHANGED
@@ -16,8 +16,6 @@ module TTY
|
|
16
16
|
include Checks
|
17
17
|
include Converters
|
18
18
|
|
19
|
-
BLANK_REGEX = /\A[[:space:]]*\z/o.freeze
|
20
|
-
|
21
19
|
UndefinedSetting = Module.new
|
22
20
|
|
23
21
|
# Store question message
|
@@ -44,6 +42,7 @@ module TTY
|
|
44
42
|
@read = options.fetch(:read) { UndefinedSetting }
|
45
43
|
@convert = options.fetch(:convert) { UndefinedSetting }
|
46
44
|
@color = options.fetch(:color) { :green }
|
45
|
+
@messages = Utils.deep_copy(options.fetch(:messages) { { } })
|
47
46
|
@done = false
|
48
47
|
@input = nil
|
49
48
|
|
@@ -56,6 +55,33 @@ module TTY
|
|
56
55
|
@evaluator << CheckModifier
|
57
56
|
end
|
58
57
|
|
58
|
+
# Stores all the error messages displayed to user
|
59
|
+
# The currently supported messages are:
|
60
|
+
# * :range?
|
61
|
+
# * :required?
|
62
|
+
# * :valid?
|
63
|
+
attr_reader :messages
|
64
|
+
|
65
|
+
# Retrieve message based on the key
|
66
|
+
#
|
67
|
+
# @param [Symbol] name
|
68
|
+
# the name of message key
|
69
|
+
#
|
70
|
+
# @param [Hash] tokens
|
71
|
+
# the tokens to evaluate
|
72
|
+
#
|
73
|
+
# @return [Array[String]]
|
74
|
+
#
|
75
|
+
# @api private
|
76
|
+
def message_for(name, tokens = nil)
|
77
|
+
template = @messages[name]
|
78
|
+
if template && !template.match(/\%\{/).nil?
|
79
|
+
[template % tokens]
|
80
|
+
else
|
81
|
+
[template || '']
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
59
85
|
# Call the question
|
60
86
|
#
|
61
87
|
# @param [String] message
|
@@ -64,7 +90,7 @@ module TTY
|
|
64
90
|
#
|
65
91
|
# @api public
|
66
92
|
def call(message, &block)
|
67
|
-
return if blank?(message)
|
93
|
+
return if Utils.blank?(message)
|
68
94
|
@message = message
|
69
95
|
block.call(self) if block
|
70
96
|
render
|
@@ -108,7 +134,7 @@ module TTY
|
|
108
134
|
# @api private
|
109
135
|
def process_input
|
110
136
|
@input = read_input
|
111
|
-
if blank?(@input)
|
137
|
+
if Utils.blank?(@input)
|
112
138
|
@input = default? ? default : nil
|
113
139
|
end
|
114
140
|
@evaluator.(@input)
|
@@ -168,7 +194,7 @@ module TTY
|
|
168
194
|
#
|
169
195
|
# @api private
|
170
196
|
def convert_result(value)
|
171
|
-
if convert? & !blank?(value)
|
197
|
+
if convert? & !Utils.blank?(value)
|
172
198
|
converter_registry.(@convert, value)
|
173
199
|
else
|
174
200
|
value
|
@@ -220,7 +246,8 @@ module TTY
|
|
220
246
|
# @return [Boolean]
|
221
247
|
#
|
222
248
|
# @api public
|
223
|
-
def required(value = (not_set = true))
|
249
|
+
def required(value = (not_set = true), message = nil)
|
250
|
+
messages[:required?] = message if message
|
224
251
|
return @required if not_set
|
225
252
|
@required = value
|
226
253
|
end
|
@@ -233,7 +260,8 @@ module TTY
|
|
233
260
|
# @return [Question]
|
234
261
|
#
|
235
262
|
# @api public
|
236
|
-
def validate(value = nil, &block)
|
263
|
+
def validate(value = nil, message = nil, &block)
|
264
|
+
messages[:valid?] = message if message
|
237
265
|
@validation = (value || block)
|
238
266
|
end
|
239
267
|
|
@@ -274,7 +302,8 @@ module TTY
|
|
274
302
|
# @param [String] value
|
275
303
|
#
|
276
304
|
# @api public
|
277
|
-
def in(value = (not_set = true))
|
305
|
+
def in(value = (not_set = true), message = nil)
|
306
|
+
messages[:range?] = message if message
|
278
307
|
if in? && !@in.is_a?(Range)
|
279
308
|
@in = converter_registry.(:range, @in)
|
280
309
|
end
|
@@ -291,12 +320,7 @@ module TTY
|
|
291
320
|
@in != UndefinedSetting
|
292
321
|
end
|
293
322
|
|
294
|
-
|
295
|
-
value.nil? ||
|
296
|
-
value.respond_to?(:empty?) && value.empty? ||
|
297
|
-
BLANK_REGEX === value
|
298
|
-
end
|
299
|
-
|
323
|
+
# @api public
|
300
324
|
def to_s
|
301
325
|
"#{message}"
|
302
326
|
end
|
@@ -40,7 +40,8 @@ module TTY
|
|
40
40
|
(question.in? && question.in.include?(cast(value)))
|
41
41
|
[value]
|
42
42
|
else
|
43
|
-
|
43
|
+
tokens = {value: value, in: question.in}
|
44
|
+
[value, question.message_for(:range?, tokens)]
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -53,7 +54,8 @@ module TTY
|
|
53
54
|
Validation.new(question.validation).call(value))
|
54
55
|
[value]
|
55
56
|
else
|
56
|
-
|
57
|
+
tokens = {valid: question.validation.inspect}
|
58
|
+
[value, question.message_for(:valid?, tokens)]
|
57
59
|
end
|
58
60
|
end
|
59
61
|
end
|
@@ -73,7 +75,7 @@ module TTY
|
|
73
75
|
class CheckRequired
|
74
76
|
def self.call(question, value)
|
75
77
|
if question.required? && !question.default? && value.nil?
|
76
|
-
[value,
|
78
|
+
[value, question.message_for(:required?)]
|
77
79
|
else
|
78
80
|
[value]
|
79
81
|
end
|
data/lib/tty/prompt/reader.rb
CHANGED
@@ -8,6 +8,10 @@ module TTY
|
|
8
8
|
# A class responsible for shell prompt interactions.
|
9
9
|
class Prompt
|
10
10
|
# A class responsible for reading character input from STDIN
|
11
|
+
#
|
12
|
+
# Used internally to provide key and line reading functionality
|
13
|
+
#
|
14
|
+
# @api private
|
11
15
|
class Reader
|
12
16
|
include Wisper::Publisher
|
13
17
|
|
@@ -56,6 +60,9 @@ module TTY
|
|
56
60
|
# Read a single keypress that may include
|
57
61
|
# 2 or 3 escape characters.
|
58
62
|
#
|
63
|
+
# @param [Boolean] echo
|
64
|
+
# whether to echo chars back or not, defaults to false
|
65
|
+
#
|
59
66
|
# @return [String]
|
60
67
|
#
|
61
68
|
# @api public
|
@@ -64,7 +71,7 @@ module TTY
|
|
64
71
|
mode.echo(echo) do
|
65
72
|
mode.raw(true) do
|
66
73
|
key = read_char
|
67
|
-
|
74
|
+
emit_key_event(key) if key
|
68
75
|
exit 130 if key == Codes::CTRL_C
|
69
76
|
key
|
70
77
|
end
|
@@ -107,7 +114,7 @@ module TTY
|
|
107
114
|
mode.echo(echo) do
|
108
115
|
while (char = input.getbyte) &&
|
109
116
|
!(char == CARRIAGE_RETURN || char == NEWLINE)
|
110
|
-
|
117
|
+
emit_key_event(convert_byte(char))
|
111
118
|
line = handle_char(line, char)
|
112
119
|
end
|
113
120
|
end
|
@@ -145,25 +152,12 @@ module TTY
|
|
145
152
|
# @return [nil]
|
146
153
|
#
|
147
154
|
# @api public
|
148
|
-
def
|
149
|
-
event = KeyEvent.from(
|
150
|
-
|
151
|
-
publish(event_name, event) unless event_name.nil?
|
155
|
+
def emit_key_event(key)
|
156
|
+
event = KeyEvent.from(key)
|
157
|
+
publish(:"key#{event.key.name}", event) if event.emit?
|
152
158
|
publish(:keypress, event)
|
153
159
|
end
|
154
160
|
|
155
|
-
# Interpret the key and provide event name
|
156
|
-
#
|
157
|
-
# @return [Symbol]
|
158
|
-
#
|
159
|
-
# @api public
|
160
|
-
def parse_key_event(event)
|
161
|
-
return if event.key.nil?
|
162
|
-
permitted_events = %w(up down left right space return enter num)
|
163
|
-
return unless permitted_events.include?("#{event.key.name}")
|
164
|
-
:"key#{event.key.name}"
|
165
|
-
end
|
166
|
-
|
167
161
|
private
|
168
162
|
|
169
163
|
trap('SIGINT') { exit 130 }
|
@@ -39,15 +39,21 @@ module TTY
|
|
39
39
|
CTRL_H = "\b"
|
40
40
|
CTRL_L = "\f"
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
42
|
+
F1_XTERM = "\eOP"
|
43
|
+
F2_XTERM = "\eOQ"
|
44
|
+
F3_XTERM = "\eOR"
|
45
|
+
F4_XTERM = "\eOS"
|
46
|
+
|
47
|
+
F1_GNOME = "\e[11~"
|
48
|
+
F2_GNOME = "\e[12~"
|
49
|
+
F3_GNOME = "\e[13~"
|
50
|
+
F4_GNOME = "\e[14~"
|
51
|
+
|
52
|
+
F1_WIN = "\e[[A"
|
53
|
+
F2_WIN = "\e[[B"
|
54
|
+
F3_WIN = "\e[[C"
|
55
|
+
F4_WIN = "\e[[D"
|
56
|
+
F5_WIN = "\e[[E"
|
51
57
|
|
52
58
|
F5 = "\e[15~"
|
53
59
|
F6 = "\e[17~"
|
@@ -21,46 +21,73 @@ module TTY
|
|
21
21
|
#
|
22
22
|
# @api public
|
23
23
|
class KeyEvent < Struct.new(:value, :key)
|
24
|
-
META_KEY_CODE_RE = /^(?:\
|
24
|
+
META_KEY_CODE_RE = /^(?:\e)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/
|
25
25
|
|
26
26
|
def self.from(char)
|
27
27
|
key = Key.new
|
28
28
|
case char
|
29
|
-
when Codes::RETURN
|
30
|
-
|
31
|
-
when Codes::
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when Codes::BACKSPACE
|
29
|
+
when Codes::RETURN then key.name = :return
|
30
|
+
when Codes::LINEFEED then key.name = :enter
|
31
|
+
when Codes::TAB then key.name = :tab
|
32
|
+
when Codes::BACKSPACE, Codes::CTRL_H,
|
33
|
+
"#{Codes::ESCAPE}#{Codes::BACKSPACE}",
|
34
|
+
"#{Codes::ESCAPE}#{Codes::CTRL_H}"
|
36
35
|
key.name = :backspace
|
37
|
-
when Codes::DELETE
|
38
|
-
|
39
|
-
when Codes::
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
key.ctrl = true
|
47
|
-
when /\d/
|
36
|
+
when Codes::DELETE then key.name = :delete
|
37
|
+
when Codes::SPACE then key.name = :space
|
38
|
+
when Codes::CTRL_C, Codes::ESCAPE then key.name = :escape
|
39
|
+
when proc { |c| c.length == 1 && c =~ /[a-z]/ }
|
40
|
+
key.name = char
|
41
|
+
when proc { |c| c.length == 1 && c =~ /[A-Z]/ }
|
42
|
+
key.name = char.downcase
|
43
|
+
key.shift = true
|
44
|
+
when /^\d+$/
|
48
45
|
key.name = :num
|
49
|
-
when META_KEY_CODE_RE
|
46
|
+
when META_KEY_CODE_RE # ansi escape
|
50
47
|
key.meta = true
|
48
|
+
|
51
49
|
case char
|
52
|
-
|
50
|
+
# f1 - f12
|
51
|
+
when Codes::F1_XTERM, Codes::F1_GNOME, Codes::F1_WIN then key.name = :f1
|
52
|
+
when Codes::F2_XTERM, Codes::F2_GNOME, Codes::F2_WIN then key.name = :f2
|
53
|
+
when Codes::F3_XTERM, Codes::F3_GNOME, Codes::F3_WIN then key.name = :f3
|
54
|
+
when Codes::F4_XTERM, Codes::F4_GNOME, Codes::F4_WIN then key.name = :f4
|
55
|
+
when Codes::F5 then key.name = :f5
|
56
|
+
when Codes::F6 then key.name = :f6
|
57
|
+
when Codes::F7 then key.name = :f7
|
58
|
+
when Codes::F8 then key.name = :f8
|
59
|
+
when Codes::F9 then key.name = :f9
|
60
|
+
when Codes::F10 then key.name = :f10
|
61
|
+
when Codes::F11 then key.name = :f11
|
62
|
+
when Codes::F12 then key.name = :f12
|
63
|
+
# navigation
|
64
|
+
when Codes::KEY_UP, Codes::KEY_UP_ALT, Codes::CTRL_K, Codes::CTRL_P
|
53
65
|
key.name = :up
|
54
|
-
when Codes::KEY_DOWN, Codes::CTRL_J, Codes::CTRL_N
|
66
|
+
when Codes::KEY_DOWN, Codes::KEY_DOWN_ALT, Codes::CTRL_J, Codes::CTRL_N
|
55
67
|
key.name = :down
|
56
|
-
when Codes::KEY_RIGHT, Codes::CTRL_L
|
68
|
+
when Codes::KEY_RIGHT, Codes::KEY_RIGHT_ALT, Codes::CTRL_L
|
57
69
|
key.name = :right
|
58
|
-
when Codes::KEY_LEFT, Codes::CTRL_H
|
70
|
+
when Codes::KEY_LEFT, Codes::KEY_LEFT_ALT, Codes::CTRL_H
|
59
71
|
key.name = :left
|
72
|
+
when Codes::KEY_CLEAR, Codes::KEY_CLEAR_ALT
|
73
|
+
key.name = :clear
|
74
|
+
when Codes::KEY_END, Codes::KEY_END_ALT
|
75
|
+
key.name = :end
|
76
|
+
when Codes::KEY_HOME, Codes::KEY_HOME_ALT
|
77
|
+
key.name = :home
|
60
78
|
end
|
61
79
|
end
|
62
80
|
new(char, key)
|
63
81
|
end
|
82
|
+
|
83
|
+
# Check if key event can be emitted
|
84
|
+
#
|
85
|
+
# @return [Boolean]
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def emit?
|
89
|
+
!key.nil? && !key.name.nil?
|
90
|
+
end
|
64
91
|
end # KeyEvent
|
65
92
|
end # Reader
|
66
93
|
end # Prompt
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
# A class responsible for shell prompt interactions.
|
5
|
+
class Prompt
|
6
|
+
# A class responsible for gathering numeric input from range
|
7
|
+
#
|
8
|
+
# @api public
|
9
|
+
class Slider
|
10
|
+
HELP = '(Use arrow keys, press Enter to select)'.freeze
|
11
|
+
|
12
|
+
# Initailize a Slider
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def initialize(prompt, options = {})
|
16
|
+
@prompt = prompt
|
17
|
+
@first_render = true
|
18
|
+
@done = false
|
19
|
+
@color = options.fetch(:color) { :green }
|
20
|
+
@min = options.fetch(:min) { 0 }
|
21
|
+
@max = options.fetch(:max) { 10 }
|
22
|
+
@step = options.fetch(:step) { 1 }
|
23
|
+
@default = options[:default]
|
24
|
+
|
25
|
+
@prompt.subscribe(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Setup initial active position
|
29
|
+
#
|
30
|
+
# @return [Integer]
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def initial
|
34
|
+
if @default.nil?
|
35
|
+
range.size / 2
|
36
|
+
else
|
37
|
+
range.index(@default)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Range of numbers to render
|
42
|
+
#
|
43
|
+
# @return [Array[Integer]]
|
44
|
+
#
|
45
|
+
# @apip private
|
46
|
+
def range
|
47
|
+
(@min..@max).step(@step).to_a
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api public
|
51
|
+
def default(value)
|
52
|
+
@default = value
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api public
|
56
|
+
def min(value)
|
57
|
+
@min = value
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api public
|
61
|
+
def max(value)
|
62
|
+
@max = value
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api public
|
66
|
+
def step(value)
|
67
|
+
@step = value
|
68
|
+
end
|
69
|
+
|
70
|
+
# Call the slider by passing question
|
71
|
+
#
|
72
|
+
# @param [String] question
|
73
|
+
# the question to ask
|
74
|
+
#
|
75
|
+
# @apu public
|
76
|
+
def call(question, &block)
|
77
|
+
@question = question
|
78
|
+
block.call(self) if block
|
79
|
+
@active = initial
|
80
|
+
render
|
81
|
+
end
|
82
|
+
|
83
|
+
def keyleft(*)
|
84
|
+
@active -= 1 if @active > 0
|
85
|
+
end
|
86
|
+
alias_method :keydown, :keyleft
|
87
|
+
|
88
|
+
def keyright(*)
|
89
|
+
@active += 1 if (@active + @step) <= range.size
|
90
|
+
end
|
91
|
+
alias_method :keyup, :keyright
|
92
|
+
|
93
|
+
def keyreturn(*)
|
94
|
+
@done = true
|
95
|
+
end
|
96
|
+
alias_method :keyspace, :keyreturn
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Render an interactive range slider.
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def render
|
104
|
+
@prompt.print(@prompt.hide)
|
105
|
+
until @done
|
106
|
+
render_question
|
107
|
+
@prompt.read_keypress
|
108
|
+
refresh
|
109
|
+
end
|
110
|
+
render_question
|
111
|
+
answer = render_answer
|
112
|
+
ensure
|
113
|
+
@prompt.print(@prompt.show)
|
114
|
+
answer
|
115
|
+
end
|
116
|
+
|
117
|
+
# Clear screen
|
118
|
+
#
|
119
|
+
# @api private
|
120
|
+
def refresh
|
121
|
+
lines = @question.scan("\n").length + 2
|
122
|
+
@prompt.print(@prompt.clear_lines(lines))
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [Integer]
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
def render_answer
|
129
|
+
range[@active]
|
130
|
+
end
|
131
|
+
|
132
|
+
# Render question with the slider
|
133
|
+
#
|
134
|
+
# @api private
|
135
|
+
def render_question
|
136
|
+
header = "#{@prompt.prefix}#{@question} #{render_header}"
|
137
|
+
@prompt.puts(header)
|
138
|
+
@first_render = false
|
139
|
+
@prompt.print(render_slider) unless @done
|
140
|
+
end
|
141
|
+
|
142
|
+
# Render actual answer or help
|
143
|
+
#
|
144
|
+
# @api private
|
145
|
+
def render_header
|
146
|
+
if @done
|
147
|
+
@prompt.decorate(render_answer.to_s, @color)
|
148
|
+
elsif @first_render
|
149
|
+
@prompt.decorate(HELP, :bright_black)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Render slider representation
|
154
|
+
#
|
155
|
+
# @return [String]
|
156
|
+
#
|
157
|
+
# @api private
|
158
|
+
def render_slider
|
159
|
+
output = ''
|
160
|
+
output << Symbols::SLIDER_END
|
161
|
+
output << '-' * @active
|
162
|
+
output << @prompt.decorate(Symbols::SLIDER_HANDLE, @color)
|
163
|
+
output << '-' * (range.size - @active - 1)
|
164
|
+
output << Symbols::SLIDER_END
|
165
|
+
output << " #{range[@active]}"
|
166
|
+
output
|
167
|
+
end
|
168
|
+
end # Slider
|
169
|
+
end # Prompt
|
170
|
+
end # TTY
|