cli-ui 1.5.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,34 +1,48 @@
1
1
  # coding: utf-8
2
+
3
+ # typed: true
4
+
2
5
  require 'io/console'
3
6
 
4
7
  module CLI
5
8
  module UI
6
9
  module Prompt
7
10
  class InteractiveOptions
11
+ extend T::Sig
12
+
8
13
  DONE = 'Done'
9
14
  CHECKBOX_ICON = { false => '☐', true => '☑' }
10
15
 
11
- # Prompts the user with options
12
- # Uses an interactive session to allow the user to pick an answer
13
- # Can use arrows, y/n, numbers (1/2), and vim bindings to control
14
- # For more than 9 options, hitting 'e', ':', or 'G' will enter select
15
- # mode allowing the user to type in longer numbers
16
- # Pressing 'f' or '/' will allow the user to filter the results
17
- #
18
- # https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif
19
- #
20
- # ==== Example Usage:
21
- #
22
- # Ask an interactive question
23
- # CLI::UI::Prompt::InteractiveOptions.call(%w(rails go python))
24
- #
25
- def self.call(options, multiple: false, default: nil)
26
- list = new(options, multiple: multiple, default: default)
27
- selected = list.call
28
- if multiple
29
- selected.map { |s| options[s - 1] }
30
- else
31
- options[selected - 1]
16
+ class << self
17
+ extend T::Sig
18
+
19
+ # Prompts the user with options
20
+ # Uses an interactive session to allow the user to pick an answer
21
+ # Can use arrows, y/n, numbers (1/2), and vim bindings to control
22
+ # For more than 9 options, hitting 'e', ':', or 'G' will enter select
23
+ # mode allowing the user to type in longer numbers
24
+ # Pressing 'f' or '/' will allow the user to filter the results
25
+ #
26
+ # https://user-images.githubusercontent.com/3074765/33797984-0ebb5e64-dcdf-11e7-9e7e-7204f279cece.gif
27
+ #
28
+ # ==== Example Usage:
29
+ #
30
+ # Ask an interactive question
31
+ # CLI::UI::Prompt::InteractiveOptions.call(%w(rails go python))
32
+ #
33
+ sig do
34
+ params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
35
+ .returns(T.any(String, T::Array[String]))
36
+ end
37
+ def call(options, multiple: false, default: nil)
38
+ list = new(options, multiple: multiple, default: default)
39
+ selected = list.call
40
+ case selected
41
+ when Array
42
+ selected.map { |s| T.must(options[s - 1]) }
43
+ else
44
+ T.must(options[selected - 1])
45
+ end
32
46
  end
33
47
  end
34
48
 
@@ -39,6 +53,10 @@ module CLI
39
53
  #
40
54
  # CLI::UI::Prompt::InteractiveOptions.new(%w(rails go python))
41
55
  #
56
+ sig do
57
+ params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(String, T::Array[String])))
58
+ .void
59
+ end
42
60
  def initialize(options, multiple: false, default: nil)
43
61
  @options = options
44
62
  @active = 1
@@ -60,12 +78,13 @@ module CLI
60
78
  end
61
79
  end
62
80
  @redraw = true
63
- @presented_options = []
81
+ @presented_options = T.let([], T::Array[[String, T.nilable(Integer)]])
64
82
  end
65
83
 
66
84
  # Calls the +InteractiveOptions+ and asks the question
67
85
  # Usually used from +self.call+
68
86
  #
87
+ sig { returns(T.any(Integer, T::Array[Integer])) }
69
88
  def call
70
89
  calculate_option_line_lengths
71
90
  CLI::UI.raw { print(ANSI.hide_cursor) }
@@ -85,6 +104,7 @@ module CLI
85
104
 
86
105
  private
87
106
 
107
+ sig { void }
88
108
  def calculate_option_line_lengths
89
109
  @terminal_width_at_calculation_time = CLI::UI::Terminal.width
90
110
  # options will be an array of questions but each option can be multi-line
@@ -103,13 +123,13 @@ module CLI
103
123
  width ||= text
104
124
  .split("\n")
105
125
  .reject(&:empty?)
106
- .map { |l| (CLI::UI.fmt(l, enable_color: false).length / max_width).ceil }
107
- .reduce(&:+)
126
+ .sum { |l| (CLI::UI.fmt(l, enable_color: false).length / max_width).ceil }
108
127
 
109
128
  width
110
129
  end
111
130
  end
112
131
 
132
+ sig { params(number_of_lines: Integer).void }
113
133
  def reset_position(number_of_lines = num_lines)
114
134
  # This will put us back at the beginning of the options
115
135
  # When we redraw the options, they will be overwritten
@@ -118,6 +138,7 @@ module CLI
118
138
  end
119
139
  end
120
140
 
141
+ sig { params(number_of_lines: Integer).void }
121
142
  def clear_output(number_of_lines = num_lines)
122
143
  CLI::UI.raw do
123
144
  # Write over all lines with whitespace
@@ -133,22 +154,26 @@ module CLI
133
154
 
134
155
  # Don't use this in place of +@displaying_metadata+, this updates too
135
156
  # quickly to be useful when drawing to the screen.
157
+ sig { returns(T::Boolean) }
136
158
  def display_metadata?
137
159
  filtering? || selecting? || has_filter?
138
160
  end
139
161
 
162
+ sig { returns(Integer) }
140
163
  def num_lines
141
164
  calculate_option_line_lengths if terminal_width_changed?
142
165
 
143
166
  option_length = presented_options.reduce(0) do |total_length, (_, option_number)|
144
167
  # Handle continuation markers and "Done" option when multiple is true
145
168
  next total_length + 1 if option_number.nil? || option_number.zero?
169
+
146
170
  total_length + @option_lengths[option_number - 1]
147
171
  end
148
172
 
149
173
  option_length + (@displaying_metadata ? 1 : 0)
150
174
  end
151
175
 
176
+ sig { returns(T::Boolean) }
152
177
  def terminal_width_changed?
153
178
  @terminal_width_at_calculation_time != CLI::UI::Terminal.width
154
179
  end
@@ -158,6 +183,7 @@ module CLI
158
183
  CTRL_C = "\u0003"
159
184
  CTRL_D = "\u0004"
160
185
 
186
+ sig { void }
161
187
  def up
162
188
  active_index = @filtered_options.index { |_, num| num == @active } || 0
163
189
 
@@ -168,6 +194,7 @@ module CLI
168
194
  @redraw = true
169
195
  end
170
196
 
197
+ sig { void }
171
198
  def down
172
199
  active_index = @filtered_options.index { |_, num| num == @active } || 0
173
200
 
@@ -180,6 +207,7 @@ module CLI
180
207
 
181
208
  # n is 1-indexed selection
182
209
  # n == 0 if "Done" was selected in @multiple mode
210
+ sig { params(n: Integer).void }
183
211
  def select_n(n)
184
212
  if @multiple
185
213
  if n == 0
@@ -200,24 +228,29 @@ module CLI
200
228
  @redraw = true
201
229
  end
202
230
 
231
+ sig { params(char: String).void }
203
232
  def select_bool(char)
204
- return unless (@options - %w(yes no)).empty?
205
- opt = @options.detect { |o| o.start_with?(char) }
206
- @active = @options.index(opt) + 1
207
- @answer = @options.index(opt) + 1
233
+ return unless (@options - ['yes', 'no']).empty?
234
+
235
+ index = T.must(@options.index { |o| o.start_with?(char) })
236
+ @active = index + 1
237
+ @answer = index + 1
208
238
  @redraw = true
209
239
  end
210
240
 
241
+ sig { params(char: String).void }
211
242
  def build_selection(char)
212
243
  @active = (@active.to_s + char).to_i
213
244
  @redraw = true
214
245
  end
215
246
 
247
+ sig { void }
216
248
  def chop_selection
217
249
  @active = @active.to_s.chop.to_i
218
250
  @redraw = true
219
251
  end
220
252
 
253
+ sig { params(char: String).void }
221
254
  def update_search(char)
222
255
  @redraw = true
223
256
 
@@ -235,25 +268,28 @@ module CLI
235
268
  end
236
269
  end
237
270
 
271
+ sig { void }
238
272
  def select_current
239
273
  # Prevent selection of invisible options
240
274
  return unless presented_options.any? { |_, num| num == @active }
275
+
241
276
  select_n(@active)
242
277
  end
243
278
 
279
+ sig { void }
244
280
  def process_input_until_redraw_required
245
281
  @redraw = false
246
282
  wait_for_user_input until @redraw
247
283
  end
248
284
 
249
285
  # rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
286
+ sig { void }
250
287
  def wait_for_user_input
251
288
  char = read_char
252
289
  @last_char = char
253
290
 
254
291
  case char
255
- when :timeout ; raise Interrupt # Timeout, use interrupt to simulate
256
- when CTRL_C ; raise Interrupt
292
+ when CTRL_C, nil ; raise Interrupt
257
293
  end
258
294
 
259
295
  max_digit = [@options.size, 9].min.to_s
@@ -302,51 +338,59 @@ module CLI
302
338
  end
303
339
  end
304
340
  end
305
- # rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon
341
+ # rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
306
342
 
343
+ sig { returns(T::Boolean) }
307
344
  def selecting?
308
345
  @state == :line_select
309
346
  end
310
347
 
348
+ sig { returns(T::Boolean) }
311
349
  def filtering?
312
350
  @state == :filter
313
351
  end
314
352
 
353
+ sig { returns(T::Boolean) }
315
354
  def has_filter?
316
355
  !@filter.empty?
317
356
  end
318
357
 
358
+ sig { void }
319
359
  def start_filter
320
360
  @state = :filter
321
361
  @redraw = true
322
362
  end
323
363
 
364
+ sig { void }
324
365
  def start_line_select
325
366
  @state = :line_select
326
367
  @active = 0
327
368
  @redraw = true
328
369
  end
329
370
 
371
+ sig { void }
330
372
  def stop_line_select
331
373
  @state = :root
332
374
  @active = 1 if @active.zero?
333
375
  @redraw = true
334
376
  end
335
377
 
378
+ sig { returns(T.nilable(String)) }
336
379
  def read_char
337
380
  if $stdin.tty? && !ENV['TEST']
338
381
  $stdin.getch # raw mode for tty
339
382
  else
340
- $stdin.getc
383
+ $stdin.getc # returns nil at end of input
341
384
  end
342
- rescue IOError
385
+ rescue Errno::EIO, Errno::EPIPE, IOError
343
386
  "\e"
344
387
  end
345
388
 
389
+ sig { params(recalculate: T::Boolean).returns(T::Array[[String, T.nilable(Integer)]]) }
346
390
  def presented_options(recalculate: false)
347
391
  return @presented_options unless recalculate
348
392
 
349
- @presented_options = @options.zip(1..Float::INFINITY)
393
+ @presented_options = @options.zip(1..)
350
394
  if has_filter?
351
395
  @presented_options.select! { |option, _| option.downcase.include?(@filter.downcase) }
352
396
  end
@@ -385,36 +429,44 @@ module CLI
385
429
  @presented_options
386
430
  end
387
431
 
432
+ sig { void }
388
433
  def ensure_visible_is_active
389
434
  unless presented_options.any? { |_, num| num == @active }
390
435
  @active = presented_options.first&.last.to_i
391
436
  end
392
437
  end
393
438
 
439
+ sig { returns(Integer) }
394
440
  def distance_from_selection_to_end
395
441
  @presented_options.count - index_of_active_option
396
442
  end
397
443
 
444
+ sig { returns(Integer) }
398
445
  def distance_from_start_to_selection
399
446
  index_of_active_option
400
447
  end
401
448
 
449
+ sig { returns(Integer) }
402
450
  def index_of_active_option
403
451
  @presented_options.index { |_, num| num == @active }.to_i
404
452
  end
405
453
 
454
+ sig { void }
406
455
  def ensure_last_item_is_continuation_marker
407
- @presented_options.push(['...', nil]) if @presented_options.last.last
456
+ @presented_options.push(['...', nil]) if @presented_options.last&.last
408
457
  end
409
458
 
459
+ sig { void }
410
460
  def ensure_first_item_is_continuation_marker
411
- @presented_options.unshift(['...', nil]) if @presented_options.first.last
461
+ @presented_options.unshift(['...', nil]) if @presented_options.first&.last
412
462
  end
413
463
 
464
+ sig { returns(Integer) }
414
465
  def max_lines
415
466
  CLI::UI::Terminal.height - (@displaying_metadata ? 3 : 2) # Keeps a one line question visible
416
467
  end
417
468
 
469
+ sig { void }
418
470
  def render_options
419
471
  previously_displayed_lines = num_lines
420
472
 
@@ -436,11 +488,7 @@ module CLI
436
488
  "Filter: #{filter_text}"
437
489
  end
438
490
 
439
- if metadata_text
440
- CLI::UI.with_frame_color(:blue) do
441
- puts CLI::UI.fmt(" {{green:#{metadata_text}}}#{ANSI.clear_to_end_of_line}")
442
- end
443
- end
491
+ puts CLI::UI.fmt(" {{green:#{metadata_text}}}#{ANSI.clear_to_end_of_line}") if metadata_text
444
492
 
445
493
  options.each do |choice, num|
446
494
  is_chosen = @multiple && num && @chosen[num - 1] && num != 0
@@ -463,12 +511,11 @@ module CLI
463
511
  message = message.split("\n").map { |l| "{{#{color}:> #{l.strip}}}" }.join("\n")
464
512
  end
465
513
 
466
- CLI::UI.with_frame_color(:blue) do
467
- puts CLI::UI.fmt(message)
468
- end
514
+ puts CLI::UI.fmt(message)
469
515
  end
470
516
  end
471
517
 
518
+ sig { params(format: String, choice: String).returns(String) }
472
519
  def format_choice(format, choice)
473
520
  eol = CLI::UI::ANSI.clear_to_end_of_line
474
521
  lines = choice.split("\n")
@@ -1,20 +1,28 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module UI
3
5
  module Prompt
4
6
  # A class that handles the various options of an InteractivePrompt and their callbacks
5
7
  class OptionsHandler
8
+ extend T::Sig
9
+
10
+ sig { void }
6
11
  def initialize
7
12
  @options = {}
8
13
  end
9
14
 
15
+ sig { returns(T::Array[String]) }
10
16
  def options
11
17
  @options.keys
12
18
  end
13
19
 
20
+ sig { params(option: String, handler: T.proc.params(option: String).returns(String)).void }
14
21
  def option(option, &handler)
15
22
  @options[option] = handler
16
23
  end
17
24
 
25
+ sig { params(options: T.any(T::Array[String], String)).returns(String) }
18
26
  def call(options)
19
27
  case options
20
28
  when Array
data/lib/cli/ui/prompt.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  # coding: utf-8
2
+
3
+ # typed: true
4
+
2
5
  require 'cli/ui'
3
6
  require 'readline'
4
7
 
@@ -22,9 +25,10 @@ module CLI
22
25
  module Prompt
23
26
  autoload :InteractiveOptions, 'cli/ui/prompt/interactive_options'
24
27
  autoload :OptionsHandler, 'cli/ui/prompt/options_handler'
25
- private_constant :InteractiveOptions, :OptionsHandler
26
28
 
27
29
  class << self
30
+ extend T::Sig
31
+
28
32
  # Ask a user a question with either free form answer or a set of answers (multiple choice)
29
33
  # Can use arrows, y/n, numbers (1/2), and vim bindings to control multiple choice selection
30
34
  # Do not use this method for yes/no questions. Use +confirm+
@@ -92,26 +96,52 @@ module CLI
92
96
  # handler.option('python') { |selection| selection }
93
97
  # end
94
98
  #
99
+ sig do
100
+ params(
101
+ question: String,
102
+ options: T.nilable(T::Array[String]),
103
+ default: T.nilable(T.any(String, T::Array[String])),
104
+ is_file: T::Boolean,
105
+ allow_empty: T::Boolean,
106
+ multiple: T::Boolean,
107
+ filter_ui: T::Boolean,
108
+ select_ui: T::Boolean,
109
+ options_proc: T.nilable(T.proc.params(handler: OptionsHandler).void),
110
+ ).returns(T.any(String, T::Array[String]))
111
+ end
95
112
  def ask(
96
113
  question,
97
114
  options: nil,
98
115
  default: nil,
99
- is_file: nil,
116
+ is_file: false,
100
117
  allow_empty: true,
101
118
  multiple: false,
102
119
  filter_ui: true,
103
120
  select_ui: true,
104
121
  &options_proc
105
122
  )
106
- if (options || block_given?) && ((default && !multiple) || is_file)
107
- raise(ArgumentError, 'conflicting arguments: options provided with default or is_file')
123
+ has_options = !!(options || block_given?)
124
+ if has_options && default && !multiple
125
+ raise(ArgumentError, 'conflicting arguments: default may not be provided with options when not multiple')
126
+ end
127
+
128
+ if has_options && is_file
129
+ raise(ArgumentError, 'conflicting arguments: is_file is only useful when options are not provided')
108
130
  end
109
131
 
110
- if options && multiple && default && !(default - options).empty?
132
+ if options && multiple && default && !(Array(default) - options).empty?
111
133
  raise(ArgumentError, 'conflicting arguments: default should only include elements present in options')
112
134
  end
113
135
 
114
- if options || block_given?
136
+ if multiple && !has_options
137
+ raise(ArgumentError, 'conflicting arguments: options must be provided when multiple is true')
138
+ end
139
+
140
+ if !multiple && default.is_a?(Array)
141
+ raise(ArgumentError, 'conflicting arguments: multiple defaults may only be provided when multiple is true')
142
+ end
143
+
144
+ if has_options
115
145
  ask_interactive(
116
146
  question,
117
147
  options,
@@ -122,7 +152,7 @@ module CLI
122
152
  &options_proc
123
153
  )
124
154
  else
125
- ask_free_form(question, default, is_file, allow_empty)
155
+ ask_free_form(question, T.cast(default, T.nilable(String)), is_file, allow_empty)
126
156
  end
127
157
  end
128
158
 
@@ -133,24 +163,23 @@ module CLI
133
163
  #
134
164
  # The password, without a trailing newline.
135
165
  # If the user simply presses "Enter" without typing any password, this will return an empty string.
166
+ sig { params(question: String).returns(String) }
136
167
  def ask_password(question)
137
168
  require 'io/console'
138
169
 
139
- CLI::UI.with_frame_color(:blue) do
140
- STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
170
+ STDOUT.print(CLI::UI.fmt('{{?}} ' + question)) # Do not use puts_question to avoid the new line.
141
171
 
142
- # noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
143
- # No fancy Readline integration (like echoing back) is required for a password prompt anyway.
144
- password = STDIN.noecho do
145
- # Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
146
- # " 123 \n".chomp => " 123 "
147
- STDIN.gets.chomp
148
- end
172
+ # noecho interacts poorly with Readline under system Ruby, so do a manual `gets` here.
173
+ # No fancy Readline integration (like echoing back) is required for a password prompt anyway.
174
+ password = STDIN.noecho do
175
+ # Chomp will remove the one new line character added by `gets`, without touching potential extra spaces:
176
+ # " 123 \n".chomp => " 123 "
177
+ STDIN.gets.to_s.chomp
178
+ end
149
179
 
150
- STDOUT.puts # Complete the line
180
+ STDOUT.puts # Complete the line
151
181
 
152
- password
153
- end
182
+ password
154
183
  end
155
184
 
156
185
  # Asks the user a yes/no question.
@@ -163,12 +192,17 @@ module CLI
163
192
  #
164
193
  # CLI::UI::Prompt.confirm('Do a dangerous thing?', default: false)
165
194
  #
195
+ sig { params(question: String, default: T::Boolean).returns(T::Boolean) }
166
196
  def confirm(question, default: true)
167
- ask_interactive(question, default ? %w(yes no) : %w(no yes), filter_ui: false) == 'yes'
197
+ ask_interactive(question, default ? ['yes', 'no'] : ['no', 'yes'], filter_ui: false) == 'yes'
168
198
  end
169
199
 
170
200
  private
171
201
 
202
+ sig do
203
+ params(question: String, default: T.nilable(String), is_file: T::Boolean, allow_empty: T::Boolean)
204
+ .returns(String)
205
+ end
172
206
  def ask_free_form(question, default, is_file, allow_empty)
173
207
  if default && !allow_empty
174
208
  raise(ArgumentError, 'conflicting arguments: default enabled but allow_empty is false')
@@ -195,6 +229,16 @@ module CLI
195
229
  end
196
230
  end
197
231
 
232
+ sig do
233
+ params(
234
+ question: String,
235
+ options: T.nilable(T::Array[String]),
236
+ multiple: T::Boolean,
237
+ default: T.nilable(T.any(String, T::Array[String])),
238
+ filter_ui: T::Boolean,
239
+ select_ui: T::Boolean,
240
+ ).returns(T.any(String, T::Array[String]))
241
+ end
198
242
  def ask_interactive(question, options = nil, multiple: false, default: nil, filter_ui: true, select_ui: true)
199
243
  raise(ArgumentError, 'conflicting arguments: options and block given') if options && block_given?
200
244
 
@@ -205,7 +249,8 @@ module CLI
205
249
  end
206
250
 
207
251
  raise(ArgumentError, 'insufficient options') if options.nil? || options.empty?
208
- navigate_text = if CLI::UI::OS.current.supports_arrow_keys?
252
+
253
+ navigate_text = if CLI::UI::OS.current.suggest_arrow_keys?
209
254
  'Choose with ↑ ↓ ⏎'
210
255
  else
211
256
  "Navigate up with 'k' and down with 'j', press Enter to select"
@@ -223,9 +268,9 @@ module CLI
223
268
  print(ANSI.previous_line + "\n")
224
269
 
225
270
  # reset the question to include the answer
226
- resp_text = resp
227
- if multiple
228
- resp_text = case resp.size
271
+ resp_text = case resp
272
+ when Array
273
+ case resp.size
229
274
  when 0
230
275
  '<nothing>'
231
276
  when 1..2
@@ -233,18 +278,26 @@ module CLI
233
278
  else
234
279
  "#{resp.size} items"
235
280
  end
281
+ else
282
+ resp
236
283
  end
237
284
  puts_question("#{question} (You chose: {{italic:#{resp_text}}})")
238
285
 
239
- return handler.call(resp) if block_given?
286
+ return T.must(handler).call(resp) if block_given?
287
+
240
288
  resp
241
289
  end
242
290
 
243
291
  # Useful for stubbing in tests
292
+ sig do
293
+ params(options: T::Array[String], multiple: T::Boolean, default: T.nilable(T.any(T::Array[String], String)))
294
+ .returns(T.any(T::Array[String], String))
295
+ end
244
296
  def interactive_prompt(options, multiple: false, default: nil)
245
297
  InteractiveOptions.call(options, multiple: multiple, default: default)
246
298
  end
247
299
 
300
+ sig { params(default: String).void }
248
301
  def write_default_over_empty_input(default)
249
302
  CLI::UI.raw do
250
303
  STDERR.puts(
@@ -252,17 +305,17 @@ module CLI
252
305
  "\r" +
253
306
  CLI::UI::ANSI.cursor_forward(4) + # TODO: width
254
307
  default +
255
- CLI::UI::Color::RESET.code
308
+ CLI::UI::Color::RESET.code,
256
309
  )
257
310
  end
258
311
  end
259
312
 
313
+ sig { params(str: String).void }
260
314
  def puts_question(str)
261
- CLI::UI.with_frame_color(:blue) do
262
- STDOUT.puts(CLI::UI.fmt('{{?}} ' + str))
263
- end
315
+ STDOUT.puts(CLI::UI.fmt('{{?}} ' + str))
264
316
  end
265
317
 
318
+ sig { params(is_file: T::Boolean).returns(String) }
266
319
  def readline(is_file: false)
267
320
  if is_file
268
321
  Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
@@ -276,11 +329,11 @@ module CLI
276
329
  # work. We could work around this by having CLI::UI use a pipe and a
277
330
  # thread to manage output, but the current strategy feels like a
278
331
  # better tradeoff.
279
- prefix = CLI::UI.with_frame_color(:blue) { CLI::UI::Frame.prefix }
332
+ prefix = CLI::UI::Frame.prefix
280
333
  # If a prompt is interrupted on Windows it locks the colour of the terminal from that point on, so we should
281
334
  # not change the colour here.
282
335
  prompt = prefix + CLI::UI.fmt('{{blue:> }}')
283
- prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.supports_color_prompt?
336
+ prompt += CLI::UI::Color::YELLOW.code if CLI::UI::OS.current.use_color_prompt?
284
337
 
285
338
  begin
286
339
  line = Readline.readline(prompt, true)