lib/cli/ui/prompt/interactive_options.rb in cli-ui-2.1.0 vs lib/cli/ui/prompt/interactive_options.rb in cli-ui-2.2.0
- old
+ new
@@ -109,25 +109,33 @@
@terminal_width_at_calculation_time = CLI::UI::Terminal.width
# options will be an array of questions but each option can be multi-line
# so to get the # of lines, you need to join then split
# since lines may be longer than the terminal is wide, we need to
- # determine how many extra lines would be taken up by them
- max_width = (@terminal_width_at_calculation_time -
- @options.count.to_s.size - # Width of the displayed number
- 5 - # Extra characters added during rendering
- (@multiple ? 1 : 0) # Space for the checkbox, if rendered
- ).to_f
+ # determine how many extra lines would be taken up by them.
+ #
+ # To accomplish this we split the string by new lines and add the
+ # extra characters to the first line.
+ # Then we calculate how many lines would be needed to render the string
+ # based on the terminal width
+ # 3 = space before the number, the . after the number, the space after the .
+ # multiple check is for the space for the checkbox, if rendered
+ # options.count.to_s.size gets us the max size of the number we will display
+ extra_chars = @marker.length + 3 + @options.count.to_s.size + (@multiple ? 1 : 0)
@option_lengths = @options.map do |text|
- width = 1 if text.empty?
- width ||= text
- .split("\n")
- .reject(&:empty?)
- .sum { |l| (CLI::UI.fmt(l, enable_color: false).length / max_width).ceil }
+ next 1 if text.empty?
- width
+ # Find the length of all the lines in this string
+ non_empty_line_lengths = text.split("\n").reject(&:empty?).map do |line|
+ CLI::UI.fmt(line, enable_color: false).length
+ end
+ # The first line has the marker and number, so we add that so we can take it into account
+ non_empty_line_lengths[0] += extra_chars
+ # Finally, we need to calculate how many lines each one will take. We can do that by dividing each one
+ # by the width of the terminal, rounding up to the nearest natural number
+ non_empty_line_lengths.sum { |length| (length.to_f / @terminal_width_at_calculation_time).ceil }
end
end
sig { params(number_of_lines: Integer).void }
def reset_position(number_of_lines = num_lines)
@@ -283,11 +291,11 @@
end
# rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon,Style/Semicolon
sig { void }
def wait_for_user_input
- char = read_char
+ char = Prompt.read_char
@last_char = char
case char
when CTRL_C, nil ; raise Interrupt
end
@@ -373,21 +381,10 @@
@state = :root
@active = 1 if @active.zero?
@redraw = true
end
- sig { returns(T.nilable(String)) }
- def read_char
- if $stdin.tty? && !ENV['TEST']
- $stdin.getch # raw mode for tty
- else
- $stdin.getc # returns nil at end of input
- end
- rescue Errno::EIO, Errno::EPIPE, IOError
- "\e"
- end
-
sig { params(recalculate: T::Boolean).returns(T::Array[[String, T.nilable(Integer)]]) }
def presented_options(recalculate: false)
return @presented_options unless recalculate
@presented_options = @options.zip(1..)
@@ -495,21 +492,23 @@
padding = ' ' * (max_num_length - num.to_s.length)
message = " #{num}#{num ? "." : " "}#{padding}"
format = '%s'
- # If multiple, bold only selected. If not multiple, bold everything
- format = "{{bold:#{format}}}" if !@multiple || is_chosen
+ # If multiple, bold selected. If not multiple, do not bold any options.
+ # Bolding options can cause confusion as some users may perceive bold white (default color) as selected
+ # rather than the actual selected color.
+ format = "{{bold:#{format}}}" if @multiple && is_chosen
format = "{{cyan:#{format}}}" if @multiple && is_chosen && num != @active
format = " #{format}"
message += format(format, CHECKBOX_ICON[is_chosen]) if @multiple && num && num > 0
message += format_choice(format, choice)
if num == @active
color = filtering? || selecting? ? 'green' : 'blue'
- message = message.split("\n").map { |l| "{{#{color}:> #{l.strip}}}" }.join("\n")
+ message = message.split("\n").map { |l| "{{#{color}:#{@marker} #{l.strip}}}" }.join("\n")
end
puts CLI::UI.fmt(message)
end
end