tty-prompt 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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