tty-prompt 0.8.0 → 0.9.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.
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class EnumPaginator < Paginator
6
+ # Paginate list of choices based on current active choice.
7
+ # Move entire pages.
8
+ #
9
+ # @api public
10
+ def paginate(list, active, per_page = nil, &block)
11
+ default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
12
+ @per_page = @per_page || per_page || default_size
13
+
14
+ # Don't paginate short lists
15
+ if list.size <= @per_page
16
+ @lower_index = 0
17
+ @upper_index = list.size - 1
18
+ if block
19
+ return list.each_with_index(&block)
20
+ else
21
+ return list.each_with_index.to_enum
22
+ end
23
+ end
24
+
25
+ unless active.nil? # User may input index out of range
26
+ @last_index = active
27
+ end
28
+ page = (@last_index / @per_page.to_f).ceil
29
+ pages = (list.size / @per_page.to_f).ceil
30
+ if page == 0
31
+ @lower_index = 0
32
+ @upper_index = @lower_index + @per_page - 1
33
+ elsif page > 0 && page <= pages
34
+ @lower_index = (page - 1) * @per_page
35
+ @upper_index = @lower_index + @per_page - 1
36
+ else
37
+ @upper_index = list.size - 1
38
+ @lower_index = @upper_index - @per_page + 1
39
+ end
40
+
41
+ sliced_list = list[@lower_index..@upper_index]
42
+ indices = (@lower_index..@upper_index)
43
+
44
+ if block
45
+ sliced_list.each_with_index do |item, index|
46
+ block[item, @lower_index + index]
47
+ end
48
+ else
49
+ sliced_list.zip(indices).to_enum unless block_given?
50
+ end
51
+ end
52
+ end # EnumPaginator
53
+ end # Prompt
54
+ end # TTY
@@ -7,7 +7,9 @@ module TTY
7
7
  #
8
8
  # @api private
9
9
  class List
10
- HELP = '(Use arrow%s keys, press Enter to select)'.freeze
10
+ HELP = '(Use arrow%s keys, press Enter to select)'
11
+
12
+ PAGE_HELP = '(Move up or down to reveal more choices)'
11
13
 
12
14
  # Create instance of TTY::Prompt::List menu.
13
15
  #
@@ -36,6 +38,9 @@ module TTY
36
38
  @help = options[:help]
37
39
  @first_render = true
38
40
  @done = false
41
+ @per_page = options[:per_page]
42
+ @page_help = options[:page_help] || PAGE_HELP
43
+ @paginator = Paginator.new
39
44
 
40
45
  @prompt.subscribe(self)
41
46
  end
@@ -54,6 +59,29 @@ module TTY
54
59
  @default = default_values
55
60
  end
56
61
 
62
+ # Set number of items per page
63
+ #
64
+ # @api public
65
+ def per_page(value)
66
+ @per_page = value
67
+ end
68
+
69
+ # Check if list is paginated
70
+ #
71
+ # @return [Boolean]
72
+ #
73
+ # @api private
74
+ def paginated?
75
+ @choices.size >= (@per_page || Paginator::DEFAULT_PAGE_SIZE)
76
+ end
77
+
78
+ # @param [String] text
79
+ # the help text to display per page
80
+ # @api pbulic
81
+ def page_help(text)
82
+ @page_help = text
83
+ end
84
+
57
85
  # Set selecting active index using number pad
58
86
  #
59
87
  # @api public
@@ -125,6 +153,7 @@ module TTY
125
153
  def keydown(*)
126
154
  @active = (@active == @choices.length) ? 1 : @active + 1
127
155
  end
156
+ alias keytab keydown
128
157
 
129
158
  private
130
159
 
@@ -163,9 +192,9 @@ module TTY
163
192
  def render
164
193
  @prompt.print(@prompt.hide)
165
194
  until @done
166
- render_question
195
+ lines = render_question
167
196
  @prompt.read_keypress
168
- refresh
197
+ refresh(lines)
169
198
  end
170
199
  render_question
171
200
  answer = render_answer
@@ -183,22 +212,30 @@ module TTY
183
212
  @choices[@active - 1].value
184
213
  end
185
214
 
186
- # Determine area of the screen to clear
215
+ # Clear screen lines
216
+ #
217
+ # @param [Integer] lines
218
+ # the lines to clear
187
219
  #
188
220
  # @api private
189
- def refresh
190
- lines = @question.scan("\n").length + @choices.length + 1
221
+ def refresh(lines)
191
222
  @prompt.print(@prompt.clear_lines(lines))
192
223
  end
193
224
 
194
225
  # Render question with instructions and menu
195
226
  #
227
+ # @return [Integer] The number of lines for menu
228
+ #
196
229
  # @api private
197
230
  def render_question
198
231
  header = "#{@prefix}#{@question} #{render_header}"
199
232
  @prompt.puts(header)
200
233
  @first_render = false
201
- @prompt.print(render_menu) unless @done
234
+ rendered_menu = render_menu
235
+ rendered_menu << render_footer
236
+ @prompt.print(rendered_menu) unless @done
237
+
238
+ header.lines.count + rendered_menu.lines.count
202
239
  end
203
240
 
204
241
  # Provide help information
@@ -228,7 +265,7 @@ module TTY
228
265
  # @api private
229
266
  def render_menu
230
267
  output = ''
231
- @choices.each_with_index do |choice, index|
268
+ @paginator.paginate(@choices, @active, @per_page) do |choice, index|
232
269
  num = enumerate? ? (index + 1).to_s + @enum + Symbols::SPACE : ''
233
270
  message = if index + 1 == @active
234
271
  selected = @marker + Symbols::SPACE + num + choice.name
@@ -236,11 +273,20 @@ module TTY
236
273
  else
237
274
  Symbols::SPACE * 2 + num + choice.name
238
275
  end
239
- newline = (index == @choices.length - 1) ? '' : "\n"
276
+ newline = (index == @paginator.max_index) ? '' : "\n"
240
277
  output << (message + newline)
241
278
  end
242
279
  output
243
280
  end
281
+
282
+ # Render page info footer
283
+ #
284
+ # @api private
285
+ def render_footer
286
+ return '' unless paginated?
287
+ colored_footer = @prompt.decorate(@page_help, @help_color)
288
+ "\n" << colored_footer
289
+ end
244
290
  end # List
245
291
  end # Prompt
246
292
  end # TTY
@@ -21,7 +21,7 @@ module TTY
21
21
  super
22
22
  @selected = []
23
23
  @help = options[:help]
24
- @default = options.fetch(:default) { [] }
24
+ @default = Array(options[:default])
25
25
  end
26
26
 
27
27
  # Callback fired when space key is pressed
@@ -84,7 +84,7 @@ module TTY
84
84
  # @api private
85
85
  def render_menu
86
86
  output = ''
87
- @choices.each_with_index do |choice, index|
87
+ @paginator.paginate(@choices, @active, @per_page) do |choice, index|
88
88
  num = enumerate? ? (index + 1).to_s + @enum + Symbols::SPACE : ''
89
89
  indicator = (index + 1 == @active) ? @marker : Symbols::SPACE
90
90
  indicator += Symbols::SPACE
@@ -94,7 +94,7 @@ module TTY
94
94
  else
95
95
  Symbols::RADIO_UNCHECKED + Symbols::SPACE + num + choice.name
96
96
  end
97
- newline = (index == @choices.length - 1) ? '' : "\n"
97
+ newline = (index == @paginator.max_index) ? '' : "\n"
98
98
  output << indicator + message + newline
99
99
  end
100
100
  output
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class Paginator
6
+ DEFAULT_PAGE_SIZE = 6
7
+
8
+ # Create a Paginator
9
+ #
10
+ # @api private
11
+ def initialize(options = {})
12
+ @last_index = Array(options[:default]).flatten.first || 0
13
+ @per_page = options[:per_page]
14
+ @lower_index = Array(options[:default]).flatten.first
15
+ end
16
+
17
+ # Maximum index for current pagination
18
+ #
19
+ # @return [Integer]
20
+ #
21
+ # @api public
22
+ def max_index
23
+ raise ArgumentError, 'no max index' unless @per_page
24
+ @lower_index + @per_page - 1
25
+ end
26
+
27
+ # Paginate collection given an active index
28
+ #
29
+ # @param [Array[Choice]] list
30
+ # a collection of choice items
31
+ # @param [Integer] active
32
+ # current choice active index
33
+ # @param [Integer] per_page
34
+ # number of choice items per page
35
+ #
36
+ # @return [Enumerable]
37
+ #
38
+ # @api public
39
+ def paginate(list, active, per_page = nil, &block)
40
+ current_index = active - 1
41
+ default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
42
+ @per_page = @per_page || per_page || default_size
43
+ @lower_index ||= current_index
44
+ @upper_index ||= max_index
45
+
46
+ # Don't paginate short lists
47
+ if list.size <= @per_page
48
+ @lower_index = 0
49
+ @upper_index = list.size - 1
50
+ if block
51
+ return list.each_with_index(&block)
52
+ else
53
+ return list.each_with_index.to_enum
54
+ end
55
+ end
56
+
57
+ if current_index > @last_index # going up
58
+ if current_index > @upper_index && current_index < list.size - 1
59
+ @lower_index += 1
60
+ end
61
+ elsif current_index < @last_index # going down
62
+ if current_index < @lower_index && current_index > 0
63
+ @lower_index -= 1
64
+ end
65
+ end
66
+
67
+ # Cycle list
68
+ if current_index.zero?
69
+ @lower_index = 0
70
+ elsif current_index == list.size - 1
71
+ @lower_index = list.size - 1 - (@per_page - 1)
72
+ end
73
+
74
+ @upper_index = @lower_index + (@per_page - 1)
75
+ @last_index = current_index
76
+
77
+ sliced_list = list[@lower_index..@upper_index]
78
+ indices = (@lower_index..@upper_index)
79
+
80
+ return sliced_list.zip(indices).to_enum unless block_given?
81
+
82
+ sliced_list.each_with_index do |item, index|
83
+ block[item, @lower_index + index]
84
+ end
85
+ end
86
+ end # Paginator
87
+ end # Prompt
88
+ end # TTY
@@ -93,22 +93,18 @@ module TTY
93
93
  #
94
94
  # @api public
95
95
  def read_char(bytes = 1)
96
- chars = input.readpartial(bytes)
97
- while CSI.start_with?(chars) ||
98
- chars.start_with?(CSI) &&
96
+ chars = input.getc
97
+ return if chars.nil?
98
+ while CSI.start_with?(chars) || chars.start_with?(CSI) &&
99
99
  !(64..126).include?(chars.each_codepoint.to_a.last)
100
- next_char = read_char(bytes + 1)
101
- chars << next_char
100
+ chars << read_char(bytes + 1)
102
101
  end
103
102
  chars
104
- rescue EOFError
105
- # Finished processing
106
- chars
107
103
  end
108
104
 
109
- # Get a single line from STDIN
110
- # Each key pressed is echoed back to the shell.
111
- # The input terminates when enter or return key is pressed.
105
+ # Get a single line from STDIN. Each key pressed is echoed
106
+ # back to the shell. The input terminates when enter or
107
+ # return key is pressed.
112
108
  #
113
109
  # @param [Boolean] echo
114
110
  # if true echo back characters, output nothing otherwise
@@ -120,10 +116,14 @@ module TTY
120
116
  line = ''
121
117
  buffer do
122
118
  mode.echo(echo) do
123
- while (char = input.getbyte) &&
124
- !(char == CARRIAGE_RETURN || char == NEWLINE)
125
- emit_key_event(convert_byte(char))
126
- line = handle_char(line, char)
119
+ while (char = read_char) && (char_byte = char.unpack('c*')[0]) &&
120
+ !(char_byte == CARRIAGE_RETURN || char_byte == NEWLINE)
121
+ emit_key_event(char)
122
+ if char_byte == BACKSPACE || char_byte == DELETE
123
+ line = line.slice(-1, 1) unless line.empty?
124
+ else
125
+ line << char
126
+ end
127
127
  end
128
128
  end
129
129
  end
@@ -168,26 +168,6 @@ module TTY
168
168
 
169
169
  private
170
170
 
171
- # Convert byte to unicode character
172
- #
173
- # @return [String]
174
- #
175
- # @api private
176
- def convert_byte(byte)
177
- Array(byte).pack('U*')
178
- end
179
-
180
- # Handle single character by appending to or removing from output
181
- #
182
- # @api private
183
- def handle_char(line, char)
184
- if char == BACKSPACE || char == DELETE
185
- line.empty? ? line : line.slice(-1, 1)
186
- else
187
- line << char
188
- end
189
- end
190
-
191
171
  # Handle input interrupt based on provided value
192
172
  #
193
173
  # @api private
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Prompt
5
- VERSION = "0.8.0"
5
+ VERSION = "0.9.0"
6
6
  end # Prompt
7
7
  end # TTY
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ RSpec.describe TTY::Prompt::EnumPaginator, '#paginate' do
4
+ it "paginates items matching per_page count" do
5
+ list = %w(a b c d e f)
6
+ paginator = described_class.new({per_page: 3})
7
+
8
+ expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
9
+ expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
10
+ expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
11
+ expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
12
+ expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
13
+ expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
14
+ expect(paginator.paginate(list, 7).to_a).to eq([['d',3], ['e',4], ['f',5]])
15
+ end
16
+
17
+ it "paginates items not matching per_page count" do
18
+ list = %w(a b c d e f g)
19
+ paginator = described_class.new({per_page: 3})
20
+
21
+ expect(paginator.paginate(list, 1).to_a).to eq([['a',0], ['b',1], ['c',2]])
22
+ expect(paginator.paginate(list, 2).to_a).to eq([['a',0], ['b',1], ['c',2]])
23
+ expect(paginator.paginate(list, 3).to_a).to eq([['a',0], ['b',1], ['c',2]])
24
+ expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
25
+ expect(paginator.paginate(list, 5).to_a).to eq([['d',3], ['e',4], ['f',5]])
26
+ expect(paginator.paginate(list, 6).to_a).to eq([['d',3], ['e',4], ['f',5]])
27
+ expect(paginator.paginate(list, 7).to_a).to eq([['g',6]])
28
+ expect(paginator.paginate(list, 8).to_a).to eq([['g',6]])
29
+ end
30
+
31
+ it "finds maximum index for current selection" do
32
+ list = %w(a b c d e f g)
33
+ paginator = described_class.new({per_page: 3, default: 0})
34
+
35
+ paginator.paginate(list, 4)
36
+ expect(paginator.max_index).to eq(5)
37
+ paginator.paginate(list, 5)
38
+ expect(paginator.max_index).to eq(5)
39
+ paginator.paginate(list, 7)
40
+ expect(paginator.max_index).to eq(8)
41
+ end
42
+
43
+ it "starts with default selection" do
44
+ list = %w(a b c d e f g)
45
+ paginator = described_class.new({per_page: 3, default: 3})
46
+
47
+ expect(paginator.paginate(list, 4).to_a).to eq([['d',3], ['e',4], ['f',5]])
48
+ end
49
+ end
@@ -1,10 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  RSpec.describe TTY::Prompt do
4
-
5
- subject(:prompt) { TTY::TestPrompt.new }
6
-
4
+ let(:page_help) {
5
+ }
7
6
  it "selects default option when return pressed immediately" do
7
+ prompt = TTY::TestPrompt.new
8
8
  choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
9
9
  prompt.input << "\n"
10
10
  prompt.input.rewind
@@ -16,13 +16,13 @@ RSpec.describe TTY::Prompt do
16
16
  " 2) /usr/bin/vim.basic\n",
17
17
  " 3) /usr/bin/vim.tiny\n",
18
18
  " Choose 1-3 [1]: ",
19
- "\e[1000D\e[K\e[1A" * 4,
20
- "\e[1000D\e[K\e[J",
19
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
21
20
  "Select an editor? \e[32m/bin/nano\e[0m\n"
22
21
  ].join)
23
22
  end
24
23
 
25
24
  it "selects option by index from the list" do
25
+ prompt = TTY::TestPrompt.new
26
26
  choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
27
27
  prompt.input << "3\n"
28
28
  prompt.input.rewind
@@ -34,18 +34,19 @@ RSpec.describe TTY::Prompt do
34
34
  "\e[32m 2) /usr/bin/vim.basic\e[0m\n",
35
35
  " 3) /usr/bin/vim.tiny\n",
36
36
  " Choose 1-3 [2]: ",
37
- "\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[J",
37
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
38
38
  "Select an editor? \n",
39
39
  " 1) /bin/nano\n",
40
40
  " 2) /usr/bin/vim.basic\n",
41
41
  "\e[32m 3) /usr/bin/vim.tiny\e[0m\n",
42
42
  " Choose 1-3 [2]: 3",
43
- "\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[1A\e[1000D\e[K\e[J",
43
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
44
44
  "Select an editor? \e[32m/usr/bin/vim.tiny\e[0m\n"
45
45
  ].join)
46
46
  end
47
47
 
48
48
  it "selects option through DSL" do
49
+ prompt = TTY::TestPrompt.new
49
50
  prompt.input << "\n"
50
51
  prompt.input.rewind
51
52
  value = prompt.enum_select("Select an editor?") do |menu|
@@ -70,6 +71,7 @@ RSpec.describe TTY::Prompt do
70
71
  end
71
72
 
72
73
  it "selects option through DSL with key and value" do
74
+ prompt = TTY::TestPrompt.new
73
75
  prompt.input << "\n"
74
76
  prompt.input.rewind
75
77
  value = prompt.enum_select("Select an editor?") do |menu|
@@ -93,6 +95,7 @@ RSpec.describe TTY::Prompt do
93
95
  end
94
96
 
95
97
  it "changes colors for selection, hint and error" do
98
+ prompt = TTY::TestPrompt.new
96
99
  choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
97
100
  prompt.input << "\n"
98
101
  prompt.input.rewind
@@ -109,4 +112,155 @@ RSpec.describe TTY::Prompt do
109
112
  "Select an editor? \e[31m/bin/nano\e[0m\n"
110
113
  ].join)
111
114
  end
115
+
116
+ it "displays error with unrecognized input" do
117
+ prompt = TTY::TestPrompt.new
118
+ choices = %w(/bin/nano /usr/bin/vim.basic /usr/bin/vim.tiny)
119
+ prompt.input << "11\n2\n"
120
+ prompt.input.rewind
121
+ value = prompt.enum_select("Select an editor?", choices)
122
+ expect(value).to eq('/usr/bin/vim.basic')
123
+ expect(prompt.output.string).to eq([
124
+ "Select an editor? \n",
125
+ "\e[32m 1) /bin/nano\e[0m\n",
126
+ " 2) /usr/bin/vim.basic\n",
127
+ " 3) /usr/bin/vim.tiny\n",
128
+ " Choose 1-3 [1]: ",
129
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
130
+ "Select an editor? \n",
131
+ "\e[32m 1) /bin/nano\e[0m\n",
132
+ " 2) /usr/bin/vim.basic\n",
133
+ " 3) /usr/bin/vim.tiny\n",
134
+ " Choose 1-3 [1]: 1",
135
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
136
+ "Select an editor? \n",
137
+ "\e[32m 1) /bin/nano\e[0m\n",
138
+ " 2) /usr/bin/vim.basic\n",
139
+ " 3) /usr/bin/vim.tiny\n",
140
+ " Choose 1-3 [1]: 11",
141
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
142
+ "Select an editor? \n",
143
+ "\e[32m 1) /bin/nano\e[0m\n",
144
+ " 2) /usr/bin/vim.basic\n",
145
+ " 3) /usr/bin/vim.tiny\n",
146
+ " Choose 1-3 [1]: \n",
147
+ "\e[31m>>\e[0m Please enter a valid number",
148
+ "\e[A\e[1G\e[18C",
149
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
150
+ "Select an editor? \n",
151
+ " 1) /bin/nano\n",
152
+ "\e[32m 2) /usr/bin/vim.basic\e[0m\n",
153
+ " 3) /usr/bin/vim.tiny\n",
154
+ " Choose 1-3 [1]: 2\n",
155
+ "\e[31m>>\e[0m Please enter a valid number",
156
+ "\e[A\e[1G\e[19C",
157
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
158
+ "Select an editor? \e[32m/usr/bin/vim.basic\e[0m\n"
159
+ ].join)
160
+ end
161
+
162
+ it "paginates long selections" do
163
+ prompt = TTY::TestPrompt.new
164
+ choices = %w(A B C D E F G H)
165
+ prompt.input << "\n"
166
+ prompt.input.rewind
167
+ value = prompt.enum_select("What letter?", choices, per_page: 3, default: 4)
168
+ expect(value).to eq('D')
169
+ expect(prompt.output.string).to eq([
170
+ "What letter? \n",
171
+ "\e[32m 4) D\e[0m\n",
172
+ " 5) E\n",
173
+ " 6) F\n",
174
+ " Choose 1-8 [4]: ",
175
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
176
+ "\e[A\e[1G\e[18C",
177
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
178
+ "What letter? \e[32mD\e[0m\n"
179
+ ].join)
180
+ end
181
+
182
+ it "shows pages matching input" do
183
+ prompt = TTY::TestPrompt.new
184
+ choices = %w(A B C D E F G H)
185
+ prompt.input << "11\n\b\n"
186
+ prompt.input.rewind
187
+ value = prompt.enum_select("What letter?", choices, per_page: 3)
188
+ expect(value).to eq('A')
189
+ expect(prompt.output.string).to eq([
190
+ "What letter? \n",
191
+ "\e[32m 1) A\e[0m\n",
192
+ " 2) B\n",
193
+ " 3) C\n",
194
+ " Choose 1-8 [1]: ",
195
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
196
+ "\e[A\e[1G\e[18C",
197
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
198
+ "What letter? \n",
199
+ "\e[32m 1) A\e[0m\n",
200
+ " 2) B\n",
201
+ " 3) C\n",
202
+ " Choose 1-8 [1]: 1",
203
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
204
+ "\e[A\e[1G\e[19C",
205
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
206
+ "What letter? \n",
207
+ "\e[32m 1) A\e[0m\n",
208
+ " 2) B\n",
209
+ " 3) C\n",
210
+ " Choose 1-8 [1]: 11",
211
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
212
+ "\e[A\e[1G\e[20C",
213
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
214
+ "What letter? \n",
215
+ "\e[32m 1) A\e[0m\n",
216
+ " 2) B\n",
217
+ " 3) C\n",
218
+ " Choose 1-8 [1]: \n",
219
+ "\e[31m>>\e[0m Please enter a valid number",
220
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
221
+ "\e[A\e[1G\e[A\e[1G\e[18C",
222
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
223
+ "What letter? \n",
224
+ "\e[32m 1) A\e[0m\n",
225
+ " 2) B\n",
226
+ " 3) C\n",
227
+ " Choose 1-8 [1]: \n",
228
+ "\e[31m>>\e[0m Please enter a valid number",
229
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
230
+ "\e[A\e[1G\e[A\e[1G\e[18C",
231
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
232
+ "What letter? \e[32mA\e[0m\n"
233
+ ].join)
234
+ end
235
+
236
+ it "switches through pages with tab key" do
237
+ prompt = TTY::TestPrompt.new
238
+ choices = %w(A B C D E F G H)
239
+ prompt.input << "\t\n"
240
+ prompt.input.rewind
241
+ value = prompt.enum_select("What letter?") do |menu|
242
+ menu.default 4
243
+ menu.per_page 3
244
+ menu.choices choices
245
+ end
246
+ expect(value).to eq('D')
247
+ expect(prompt.output.string).to eq([
248
+ "What letter? \n",
249
+ "\e[32m 4) D\e[0m\n",
250
+ " 5) E\n",
251
+ " 6) F\n",
252
+ " Choose 1-8 [4]: ",
253
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
254
+ "\e[A\e[1G\e[18C",
255
+ "\e[1000D\e[K\e[1A" * 4 + "\e[1000D\e[K\e[J",
256
+ "What letter? \n",
257
+ " 7) G\n",
258
+ " 8) H\n",
259
+ " Choose 1-8 [4]: ",
260
+ "\n\e[90m(Press tab/right or left to reveal more choices)\e[0m",
261
+ "\e[A\e[1G\e[18C",
262
+ "\e[1000D\e[K\e[1A" * 3 + "\e[1000D\e[K\e[J",
263
+ "What letter? \e[32mD\e[0m\n"
264
+ ].join)
265
+ end
112
266
  end