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