shopify-cli 0.9.2 โ†’ 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/docs/_config.yml +3 -0
  4. data/docs/_data/nav.yml +9 -0
  5. data/docs/getting-started/index.md +5 -40
  6. data/docs/getting-started/install/index.md +39 -0
  7. data/docs/getting-started/migrate/index.md +63 -0
  8. data/docs/getting-started/uninstall/index.md +37 -0
  9. data/docs/getting-started/upgrade/index.md +37 -0
  10. data/docs/index.md +5 -6
  11. data/lib/project_types/extension/cli.rb +2 -1
  12. data/lib/project_types/extension/commands/tunnel.rb +1 -1
  13. data/lib/project_types/extension/forms/register.rb +2 -3
  14. data/lib/project_types/extension/graphql/get_app_by_api_key.graphql +9 -0
  15. data/lib/project_types/extension/tasks/converters/app_converter.rb +27 -0
  16. data/lib/project_types/extension/tasks/get_app.rb +22 -0
  17. data/lib/project_types/extension/tasks/get_apps.rb +1 -6
  18. data/lib/project_types/script/cli.rb +3 -2
  19. data/lib/project_types/script/commands/create.rb +5 -4
  20. data/lib/project_types/script/errors.rb +1 -0
  21. data/lib/project_types/script/forms/create.rb +8 -4
  22. data/lib/project_types/script/layers/application/create_script.rb +11 -18
  23. data/lib/project_types/script/layers/application/project_dependencies.rb +0 -5
  24. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +106 -0
  25. data/lib/project_types/script/layers/infrastructure/errors.rb +1 -1
  26. data/lib/project_types/script/layers/infrastructure/project_creator.rb +23 -0
  27. data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -33
  28. data/lib/project_types/script/messages/messages.rb +5 -6
  29. data/lib/project_types/script/templates/ts/as-pect.d.ts +1 -0
  30. data/lib/project_types/script/ui/error_handler.rb +5 -0
  31. data/lib/shopify-cli/tunnel.rb +33 -1
  32. data/lib/shopify-cli/version.rb +1 -1
  33. data/vendor/deps/cli-ui/REVISION +1 -1
  34. data/vendor/deps/cli-ui/lib/cli/ui.rb +52 -11
  35. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +11 -7
  36. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +34 -21
  37. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +107 -149
  38. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +99 -0
  39. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +119 -0
  40. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +158 -0
  41. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +112 -0
  42. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +9 -15
  43. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +47 -0
  44. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +9 -7
  45. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +39 -14
  46. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +62 -44
  47. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +7 -2
  48. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +23 -3
  49. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +34 -10
  50. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +12 -7
  51. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +26 -16
  52. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +3 -3
  53. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +75 -0
  54. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  55. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  56. metadata +20 -7
  57. data/lib/project_types/extension/features/tunnel_url.rb +0 -20
  58. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +0 -51
  59. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +0 -36
  60. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +0 -62
  61. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +0 -15
@@ -29,7 +29,7 @@ module CLI
29
29
  @handle = handle
30
30
  @codepoint = codepoint
31
31
  @color = color
32
- @char = [codepoint].pack('U')
32
+ @char = Array(codepoint).pack('U*')
33
33
  @to_s = color.code + char + Color::RESET.code
34
34
  @fmt = "{{#{color.name}:#{char}}}"
35
35
 
@@ -38,20 +38,14 @@ module CLI
38
38
 
39
39
  # Mapping of glyphs to terminal output
40
40
  MAP = {}
41
- # YELLOw SMALL STAR (โญ‘)
42
- STAR = new('*', 0x2b51, Color::YELLOW)
43
- # BLUE MATHEMATICAL SCRIPT SMALL i (๐’พ)
44
- INFO = new('i', 0x1d4be, Color::BLUE)
45
- # BLUE QUESTION MARK (?)
46
- QUESTION = new('?', 0x003f, Color::BLUE)
47
- # GREEN CHECK MARK (โœ”๏ธŽ)
48
- CHECK = new('v', 0x2713, Color::GREEN)
49
- # RED BALLOT X (โœ—)
50
- X = new('x', 0x2717, Color::RED)
51
- # Bug emoji (๐Ÿ›)
52
- BUG = new('b', 0x1f41b, Color::WHITE)
53
- # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (ยป)
54
- CHEVRON = new('>', 0xbb, Color::YELLOW)
41
+ STAR = new('*', 0x2b51, Color::YELLOW) # YELLOW SMALL STAR (โญ‘)
42
+ INFO = new('i', 0x1d4be, Color::BLUE) # BLUE MATHEMATICAL SCRIPT SMALL i (๐’พ)
43
+ QUESTION = new('?', 0x003f, Color::BLUE) # BLUE QUESTION MARK (?)
44
+ CHECK = new('v', 0x2713, Color::GREEN) # GREEN CHECK MARK (โœ“)
45
+ X = new('x', 0x2717, Color::RED) # RED BALLOT X (โœ—)
46
+ BUG = new('b', 0x1f41b, Color::WHITE) # Bug emoji (๐Ÿ›)
47
+ CHEVRON = new('>', 0xbb, Color::YELLOW) # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK (ยป)
48
+ HOURGLASS = new('H', [0x231b, 0xfe0e], Color::BLUE) # HOURGLASS + VARIATION SELECTOR 15 (โŒ›๏ธŽ)
55
49
 
56
50
  # Looks up a glyph by name
57
51
  #
@@ -0,0 +1,47 @@
1
+ require 'cli/ui'
2
+
3
+ module CLI
4
+ module UI
5
+ class Printer
6
+ # Print a message to a stream with common utilities.
7
+ # Allows overriding the color, encoding, and target stream.
8
+ # By default, it formats the string using CLI:UI and rescues common stream errors.
9
+ #
10
+ # ==== Attributes
11
+ #
12
+ # * +msg+ - (required) the string to output. Can be frozen.
13
+ #
14
+ # ==== Options
15
+ #
16
+ # * +:frame_color+ - Override the frame color. Defaults to nil.
17
+ # * +:to+ - Target stream, like $stdout or $stderr. Can be anything with a puts method. Defaults to $stdout.
18
+ # * +:encoding+ - Force the output to be in a certain encoding. Defaults to UTF-8.
19
+ # * +:format+ - Whether to format the string using CLI::UI.fmt. Defaults to true.
20
+ # * +:graceful+ - Whether to gracefully ignore common I/O errors. Defaults to true.
21
+ #
22
+ # ==== Returns
23
+ # Returns whether the message was successfully printed,
24
+ # which can be useful if +:graceful+ is set to true.
25
+ #
26
+ # ==== Example
27
+ #
28
+ # CLI::UI::Printer.puts('{x} Ouch', stream: $stderr, color: :red)
29
+ #
30
+ def self.puts(msg, frame_color: nil, to: $stdout, encoding: Encoding::UTF_8, format: true, graceful: true)
31
+ msg = (+msg).force_encoding(encoding) if encoding
32
+ msg = CLI::UI.fmt(msg) if format
33
+
34
+ if frame_color
35
+ CLI::UI::Frame.with_frame_color_override(frame_color) { to.puts(msg) }
36
+ else
37
+ to.puts(msg)
38
+ end
39
+
40
+ true
41
+ rescue Errno::EIO, Errno::EPIPE, IOError => e
42
+ raise(e) unless graceful
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
@@ -26,11 +26,11 @@ module CLI
26
26
  #
27
27
  # Increase the percent by X
28
28
  # CLI::UI::Progress.progress do |bar|
29
- # bar.tick(percent: 5)
29
+ # bar.tick(percent: 0.05)
30
30
  # end
31
31
  def self.progress(width: Terminal.width)
32
32
  bar = Progress.new(width: width)
33
- print CLI::UI::ANSI.hide_cursor
33
+ print(CLI::UI::ANSI.hide_cursor)
34
34
  yield(bar)
35
35
  ensure
36
36
  puts bar.to_s
@@ -59,14 +59,16 @@ module CLI
59
59
  # * +:percent+ - Increment progress by a specific percent amount
60
60
  # * +:set_percent+ - Set progress to a specific percent
61
61
  #
62
+ # *Note:* The +:percent+ and +:set_percent must be between 0.00 and 1.0
63
+ #
62
64
  def tick(percent: 0.01, set_percent: nil)
63
65
  raise ArgumentError, 'percent and set_percent cannot both be specified' if percent != 0.01 && set_percent
64
66
  @percent_done += percent
65
67
  @percent_done = set_percent if set_percent
66
68
  @percent_done = [@percent_done, 1.0].min # Make sure we can't go above 1.0
67
69
 
68
- print to_s
69
- print CLI::UI::ANSI.previous_line + "\n"
70
+ print(to_s)
71
+ print(CLI::UI::ANSI.previous_line + "\n")
70
72
  end
71
73
 
72
74
  # Format the progress bar to be printed to terminal
@@ -77,11 +79,11 @@ module CLI
77
79
  filled = [(@percent_done * workable_width.to_f).ceil, 0].max
78
80
  unfilled = [workable_width - filled, 0].max
79
81
 
80
- CLI::UI.resolve_text [
82
+ CLI::UI.resolve_text([
81
83
  FILLED_BAR + ' ' * filled,
82
84
  UNFILLED_BAR + ' ' * unfilled,
83
- CLI::UI::Color::RESET.code + suffix
84
- ].join
85
+ CLI::UI::Color::RESET.code + suffix,
86
+ ].join)
85
87
  end
86
88
  end
87
89
  end
@@ -36,7 +36,8 @@ module CLI
36
36
  # * +:select_ui+ - Enable long-form option selection (default: true)
37
37
  #
38
38
  # Note:
39
- # * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+, you cannot set options with either of these keywords
39
+ # * +:options+ or providing a +Block+ conflicts with +:default+ and +:is_file+,
40
+ # you cannot set options with either of these keywords
40
41
  # * +:default+ conflicts with +:allow_empty:, you cannot set these together
41
42
  # * +:options+ conflicts with providing a +Block+ , you may only set one
42
43
  # * +:multiple+ can only be used with +:options+ or a +Block+; it is ignored, otherwise.
@@ -49,7 +50,7 @@ module CLI
49
50
  # ==== Return Value
50
51
  #
51
52
  # * If a +Block+ was not provided, the selected option or response to the free form question will be returned
52
- # * If a +Block+ was provided, the evaluted value of the +Block+ will be returned
53
+ # * If a +Block+ was provided, the evaluated value of the +Block+ will be returned
53
54
  #
54
55
  # ==== Example Usage:
55
56
  #
@@ -76,13 +77,35 @@ module CLI
76
77
  # handler.option('python') { |selection| selection }
77
78
  # end
78
79
  #
79
- def ask(question, options: nil, default: nil, is_file: nil, allow_empty: true, multiple: false, filter_ui: true, select_ui: true, &options_proc)
80
- if ((options || block_given?) && (default || is_file))
80
+ def ask(
81
+ question,
82
+ options: nil,
83
+ default: nil,
84
+ is_file: nil,
85
+ allow_empty: true,
86
+ multiple: false,
87
+ filter_ui: true,
88
+ select_ui: true,
89
+ &options_proc
90
+ )
91
+ if (options || block_given?) && ((default && !multiple) || is_file)
81
92
  raise(ArgumentError, 'conflicting arguments: options provided with default or is_file')
82
93
  end
83
94
 
95
+ if options && multiple && default && !(default - options).empty?
96
+ raise(ArgumentError, 'conflicting arguments: default should only include elements present in options')
97
+ end
98
+
84
99
  if options || block_given?
85
- ask_interactive(question, options, multiple: multiple, filter_ui: filter_ui, select_ui: select_ui, &options_proc)
100
+ ask_interactive(
101
+ question,
102
+ options,
103
+ multiple: multiple,
104
+ default: default,
105
+ filter_ui: filter_ui,
106
+ select_ui: select_ui,
107
+ &options_proc
108
+ )
86
109
  else
87
110
  ask_free_form(question, default, is_file, allow_empty)
88
111
  end
@@ -132,7 +155,9 @@ module CLI
132
155
  private
133
156
 
134
157
  def ask_free_form(question, default, is_file, allow_empty)
135
- raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false') if (default && !allow_empty)
158
+ if default && !allow_empty
159
+ raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false')
160
+ end
136
161
 
137
162
  if default
138
163
  puts_question("#{question} (empty = #{default})")
@@ -155,7 +180,7 @@ module CLI
155
180
  end
156
181
  end
157
182
 
158
- def ask_interactive(question, options = nil, multiple: false, filter_ui: true, select_ui: true)
183
+ def ask_interactive(question, options = nil, multiple: false, default: nil, filter_ui: true, select_ui: true)
159
184
  raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
160
185
 
161
186
  options ||= if block_given?
@@ -167,14 +192,14 @@ module CLI
167
192
  raise(ArgumentError, 'insufficient options') if options.nil? || options.empty?
168
193
  instructions = (multiple ? "Toggle options. " : "") + "Choose with โ†‘ โ†“ โŽ"
169
194
  instructions += ", filter with 'f'" if filter_ui
170
- instructions += ", enter option with 'e'" if select_ui and options.size > 9
195
+ instructions += ", enter option with 'e'" if select_ui && (options.size > 9)
171
196
  puts_question("#{question} {{yellow:(#{instructions})}}")
172
- resp = interactive_prompt(options, multiple: multiple)
197
+ resp = interactive_prompt(options, multiple: multiple, default: default)
173
198
 
174
199
  # Clear the line
175
- print ANSI.previous_line + ANSI.clear_to_end_of_line
200
+ print(ANSI.previous_line + ANSI.clear_to_end_of_line)
176
201
  # Force StdoutRouter to prefix
177
- print ANSI.previous_line + "\n"
202
+ print(ANSI.previous_line + "\n")
178
203
 
179
204
  # reset the question to include the answer
180
205
  resp_text = resp
@@ -195,8 +220,8 @@ module CLI
195
220
  end
196
221
 
197
222
  # Useful for stubbing in tests
198
- def interactive_prompt(options, multiple: false)
199
- InteractiveOptions.call(options, multiple: multiple)
223
+ def interactive_prompt(options, multiple: false, default: nil)
224
+ InteractiveOptions.call(options, multiple: multiple, default: default)
200
225
  end
201
226
 
202
227
  def write_default_over_empty_input(default)
@@ -235,7 +260,7 @@ module CLI
235
260
 
236
261
  begin
237
262
  line = Readline.readline(prompt, true)
238
- print CLI::UI::Color::RESET.code
263
+ print(CLI::UI::Color::RESET.code)
239
264
  line.to_s.chomp
240
265
  rescue Interrupt
241
266
  CLI::UI.raw { STDERR.puts('^C' + CLI::UI::Color::RESET.code) }
@@ -22,8 +22,8 @@ module CLI
22
22
  # Ask an interactive question
23
23
  # CLI::UI::Prompt::InteractiveOptions.call(%w(rails go python))
24
24
  #
25
- def self.call(options, multiple: false)
26
- list = new(options, multiple: multiple)
25
+ def self.call(options, multiple: false, default: nil)
26
+ list = new(options, multiple: multiple, default: default)
27
27
  selected = list.call
28
28
  if multiple
29
29
  selected.map { |s| options[s - 1] }
@@ -39,7 +39,7 @@ module CLI
39
39
  #
40
40
  # CLI::UI::Prompt::InteractiveOptions.new(%w(rails go python))
41
41
  #
42
- def initialize(options, multiple: false)
42
+ def initialize(options, multiple: false, default: nil)
43
43
  @options = options
44
44
  @active = 1
45
45
  @marker = '>'
@@ -52,7 +52,13 @@ module CLI
52
52
  @filter = ''
53
53
  # 0-indexed array representing if selected
54
54
  # @options[0] is selected if @chosen[0]
55
- @chosen = Array.new(@options.size) { false } if multiple
55
+ if multiple
56
+ @chosen = if default
57
+ @options.map { |option| default.include?(option) }
58
+ else
59
+ Array.new(@options.size) { false }
60
+ end
61
+ end
56
62
  @redraw = true
57
63
  @presented_options = []
58
64
  end
@@ -95,16 +101,16 @@ module CLI
95
101
  @option_lengths = @options.map do |text|
96
102
  width = 1 if text.empty?
97
103
  width ||= text
98
- .split("\n")
99
- .reject(&:empty?)
100
- .map { |l| (l.length / max_width).ceil }
101
- .reduce(&:+)
104
+ .split("\n")
105
+ .reject(&:empty?)
106
+ .map { |l| (CLI::UI.fmt(l, enable_color: false).length / max_width).ceil }
107
+ .reduce(&:+)
102
108
 
103
109
  width
104
110
  end
105
111
  end
106
112
 
107
- def reset_position(number_of_lines=num_lines)
113
+ def reset_position(number_of_lines = num_lines)
108
114
  # This will put us back at the beginning of the options
109
115
  # When we redraw the options, they will be overwritten
110
116
  CLI::UI.raw do
@@ -112,12 +118,12 @@ module CLI
112
118
  end
113
119
  end
114
120
 
115
- def clear_output(number_of_lines=num_lines)
121
+ def clear_output(number_of_lines = num_lines)
116
122
  CLI::UI.raw do
117
123
  # Write over all lines with whitespace
118
124
  number_of_lines.times { puts(' ' * CLI::UI::Terminal.width) }
119
125
  end
120
- reset_position number_of_lines
126
+ reset_position(number_of_lines)
121
127
 
122
128
  # Update if metadata is being displayed
123
129
  # This must be done _after_ the output is cleared or it won't draw over
@@ -128,7 +134,7 @@ module CLI
128
134
  # Don't use this in place of +@displaying_metadata+, this updates too
129
135
  # quickly to be useful when drawing to the screen.
130
136
  def display_metadata?
131
- filtering? or selecting? or has_filter?
137
+ filtering? || selecting? || has_filter?
132
138
  end
133
139
 
134
140
  def num_lines
@@ -136,7 +142,7 @@ module CLI
136
142
 
137
143
  option_length = presented_options.reduce(0) do |total_length, (_, option_number)|
138
144
  # Handle continuation markers and "Done" option when multiple is true
139
- next total_length + 1 if option_number.nil? or option_number.zero?
145
+ next total_length + 1 if option_number.nil? || option_number.zero?
140
146
  total_length + @option_lengths[option_number - 1]
141
147
  end
142
148
 
@@ -153,7 +159,7 @@ module CLI
153
159
  CTRL_D = "\u0004"
154
160
 
155
161
  def up
156
- active_index = @filtered_options.index { |_,num| num == @active } || 0
162
+ active_index = @filtered_options.index { |_, num| num == @active } || 0
157
163
 
158
164
  previous_visible = @filtered_options[active_index - 1]
159
165
  previous_visible ||= @filtered_options.last
@@ -163,7 +169,7 @@ module CLI
163
169
  end
164
170
 
165
171
  def down
166
- active_index = @filtered_options.index { |_,num| num == @active } || 0
172
+ active_index = @filtered_options.index { |_, num| num == @active } || 0
167
173
 
168
174
  next_visible = @filtered_options[active_index + 1]
169
175
  next_visible ||= @filtered_options.first
@@ -216,7 +222,7 @@ module CLI
216
222
  @redraw = true
217
223
 
218
224
  # Control+D or Backspace on empty search closes search
219
- if char == CTRL_D or (@filter.empty? and char == BACKSPACE)
225
+ if (char == CTRL_D) || (@filter.empty? && (char == BACKSPACE))
220
226
  @filter = ''
221
227
  @state = :root
222
228
  return
@@ -231,7 +237,7 @@ module CLI
231
237
 
232
238
  def select_current
233
239
  # Prevent selection of invisible options
234
- return unless presented_options.any? { |_,num| num == @active }
240
+ return unless presented_options.any? { |_, num| num == @active }
235
241
  select_n(@active)
236
242
  end
237
243
 
@@ -240,7 +246,7 @@ module CLI
240
246
  wait_for_user_input until @redraw
241
247
  end
242
248
 
243
- # rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon
249
+ # rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
244
250
  def wait_for_user_input
245
251
  char = read_char
246
252
  @last_char = char
@@ -250,17 +256,18 @@ module CLI
250
256
  when CTRL_C ; raise Interrupt
251
257
  end
252
258
 
259
+ max_digit = [@options.size, 9].min.to_s
253
260
  case @state
254
261
  when :root
255
262
  case char
256
- when ESC ; @state = :esc
257
- when 'k' ; up
258
- when 'j' ; down
259
- when 'e', ':', 'G' ; start_line_select
260
- when 'f', '/' ; start_filter
261
- when ('0'..@options.size.to_s) ; select_n(char.to_i)
262
- when 'y', 'n' ; select_bool(char)
263
- when " ", "\r", "\n" ; select_current # <enter>
263
+ when ESC ; @state = :esc
264
+ when 'k' ; up
265
+ when 'j' ; down
266
+ when 'e', ':', 'G' ; start_line_select
267
+ when 'f', '/' ; start_filter
268
+ when ('0'..max_digit) ; select_n(char.to_i)
269
+ when 'y', 'n' ; select_bool(char)
270
+ when " ", "\r", "\n" ; select_current # <enter>
264
271
  end
265
272
  when :filter
266
273
  case char
@@ -273,9 +280,9 @@ module CLI
273
280
  when ESC ; @state = :esc
274
281
  when 'k' ; up ; @state = :root
275
282
  when 'j' ; down ; @state = :root
276
- when 'e',':','G','q' ; stop_line_select
283
+ when 'e', ':', 'G', 'q' ; stop_line_select
277
284
  when '0'..'9' ; build_selection(char)
278
- when BACKSPACE ; chop_selection # Pop last input on backspace
285
+ when BACKSPACE ; chop_selection # Pop last input on backspace
279
286
  when ' ', "\r", "\n" ; select_current
280
287
  end
281
288
  when :esc
@@ -347,16 +354,27 @@ module CLI
347
354
 
348
355
  @presented_options = @options.zip(1..Float::INFINITY)
349
356
  if has_filter?
350
- @presented_options.select! { |option,_| option.downcase.include?(@filter.downcase) }
357
+ @presented_options.select! { |option, _| option.downcase.include?(@filter.downcase) }
351
358
  end
352
359
 
353
360
  # Used for selection purposes
361
+ @presented_options.push([DONE, 0]) if @multiple
354
362
  @filtered_options = @presented_options.dup
355
363
 
356
- @presented_options.unshift([DONE, 0]) if @multiple
357
-
358
364
  ensure_visible_is_active if has_filter?
359
365
 
366
+ # Must have more lines before the selection than we can display
367
+ if distance_from_start_to_selection > max_lines
368
+ @presented_options.shift(distance_from_start_to_selection - max_lines)
369
+ ensure_first_item_is_continuation_marker
370
+ end
371
+
372
+ # Must have more lines after the selection than we can display
373
+ if distance_from_selection_to_end > max_lines
374
+ @presented_options.pop(distance_from_selection_to_end - max_lines)
375
+ ensure_last_item_is_continuation_marker
376
+ end
377
+
360
378
  while num_lines > max_lines
361
379
  # try to keep the selection centered in the window:
362
380
  if distance_from_selection_to_end > distance_from_start_to_selection
@@ -388,7 +406,7 @@ module CLI
388
406
  end
389
407
 
390
408
  def index_of_active_option
391
- @presented_options.index { |_,num| num == @active }.to_i
409
+ @presented_options.index { |_, num| num == @active }.to_i
392
410
  end
393
411
 
394
412
  def ensure_last_item_is_continuation_marker
@@ -415,14 +433,14 @@ module CLI
415
433
  max_num_length = (@options.size + 1).to_s.length
416
434
 
417
435
  metadata_text = if selecting?
418
- select_text = @active
419
- select_text = '{{info:e, q, or up/down anytime to exit}}' if @active == 0
420
- "Select: #{select_text}"
421
- elsif filtering? or has_filter?
422
- filter_text = @filter
423
- filter_text = '{{info:Ctrl-D anytime or Backspace now to exit}}' if @filter.empty?
424
- "Filter: #{filter_text}"
425
- end
436
+ select_text = @active
437
+ select_text = '{{info:e, q, or up/down anytime to exit}}' if @active == 0
438
+ "Select: #{select_text}"
439
+ elsif filtering? || has_filter?
440
+ filter_text = @filter
441
+ filter_text = '{{info:Ctrl-D anytime or Backspace now to exit}}' if @filter.empty?
442
+ "Filter: #{filter_text}"
443
+ end
426
444
 
427
445
  if metadata_text
428
446
  CLI::UI.with_frame_color(:blue) do
@@ -431,7 +449,7 @@ module CLI
431
449
  end
432
450
 
433
451
  options.each do |choice, num|
434
- is_chosen = @multiple && num && @chosen[num - 1]
452
+ is_chosen = @multiple && num && @chosen[num - 1] && num != 0
435
453
 
436
454
  padding = ' ' * (max_num_length - num.to_s.length)
437
455
  message = " #{num}#{num ? '.' : ' '}#{padding}"
@@ -442,12 +460,12 @@ module CLI
442
460
  format = "{{cyan:#{format}}}" if @multiple && is_chosen && num != @active
443
461
  format = " #{format}"
444
462
 
445
- message += sprintf(format, CHECKBOX_ICON[is_chosen]) if @multiple && num && num > 0
463
+ message += format(format, CHECKBOX_ICON[is_chosen]) if @multiple && num && num > 0
446
464
  message += format_choice(format, choice)
447
465
 
448
466
  if num == @active
449
467
 
450
- color = (filtering? or selecting?) ? 'green' : 'blue'
468
+ color = filtering? || selecting? ? 'green' : 'blue'
451
469
  message = message.split("\n").map { |l| "{{#{color}:> #{l.strip}}}" }.join("\n")
452
470
  end
453
471
 
@@ -463,7 +481,7 @@ module CLI
463
481
 
464
482
  return eol if lines.empty? # Handle blank options
465
483
 
466
- lines.map! { |l| sprintf(format, l) + eol }
484
+ lines.map! { |l| format(format, l) + eol }
467
485
  lines.join("\n")
468
486
  end
469
487
  end