shopify-cli 0.9.2 โ†’ 0.9.3

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.
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