class Dev::UI::Prompt::InteractiveOptions

Constants

ESC

Public Class Methods

call(options) click to toggle source

Prompts the user with options Uses an interactive session to allow the user to pick an answer Can use arrows, y/n, numbers (1/2), and vim bindings to control

Example Usage:

Ask an interactive question

Dev::UI::Prompt::InteractiveOptions.call(%w(rails go python))
# File lib/dev/ui/prompt/interactive_options.rb, line 18
def self.call(options)
  list = new(options)
  options[list.call - 1]
end
new(options) click to toggle source

Initializes a new InteractiveOptions Usually called from self.call

Example Usage:

Dev::UI::Prompt::InteractiveOptions.new(%w(rails go python))
# File lib/dev/ui/prompt/interactive_options.rb, line 30
def initialize(options)
  @options = options
  @active = 1
  @marker = '>'
  @answer = nil
  @state = :root
end

Public Instance Methods

call() click to toggle source

Calls the InteractiveOptions and asks the question Usually used from self.call

# File lib/dev/ui/prompt/interactive_options.rb, line 41
def call
  Dev::UI.raw { print(ANSI.hide_cursor) }
  while @answer.nil?
    render_options
    wait_for_user_input
    reset_position
  end
  clear_output
  @answer
ensure
  Dev::UI.raw do
    print(ANSI.show_cursor)
    puts(ANSI.previous_line + ANSI.end_of_line)
  end
end

Private Instance Methods

clear_output() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 68
def clear_output
  Dev::UI.raw do
    # Write over all lines with whitespace
    num_lines.times { puts(' ' * Dev::UI::Terminal.width) }
  end
  reset_position
end
down() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 89
def down
  @active = @active + 1 <= @options.length ? @active + 1 : 1
end
num_lines() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 76
def num_lines
  # @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
  joined_options = @options.join("\n")
  joined_options.split("\n").reject(&:empty?).size
end
raw_tty!() { || ... } click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 141
def raw_tty!
  if ENV['TEST'] || !$stdin.tty?
    yield
  else
    $stdin.raw { yield }
  end
end
read_char() click to toggle source

rubocop:enable Style/WhenThen,Layout/SpaceBeforeSemicolon

# File lib/dev/ui/prompt/interactive_options.rb, line 135
def read_char
  raw_tty! { $stdin.getc.chr }
rescue IOError
  "\e"
end
render_options() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 149
def render_options
  @options.each_with_index do |choice, index|
    num = index + 1
    message = "  #{num}."
    message += choice.split("\n").map { |l| " {{bold:#{l}}}" }.join("\n")

    if num == @active
      message = message.split("\n").map.with_index do |l, idx|
        idx == 0 ? "{{blue:> #{l.strip}}}" : "{{blue:>#{l.strip}}}"
      end.join("\n")
    end

    Dev::UI.with_frame_color(:blue) do
      puts Dev::UI.fmt(message)
    end
  end
end
reset_position() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 59
def reset_position
  # This will put us back at the beginning of the options
  # When we redraw the options, they will be overwritten
  Dev::UI.raw do
    num_lines.times { print(ANSI.previous_line) }
    print(ANSI.previous_line + ANSI.end_of_line + "\n")
  end
end
select_bool(char) click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 98
def select_bool(char)
  return unless (@options - %w(yes no)).empty?
  opt = @options.detect { |o| o.start_with?(char) }
  @active = @options.index(opt) + 1
  @answer = @options.index(opt) + 1
end
select_n(n) click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 93
def select_n(n)
  @active = n
  @answer = n
end
up() click to toggle source
# File lib/dev/ui/prompt/interactive_options.rb, line 85
def up
  @active = @active - 1 >= 1 ? @active - 1 : @options.length
end
wait_for_user_input() click to toggle source

rubocop:disable Style/WhenThen,Layout/SpaceBeforeSemicolon

# File lib/dev/ui/prompt/interactive_options.rb, line 106
def wait_for_user_input
  char = read_char
  case @state
  when :root
    case char
    when ESC                       ; @state = :esc
    when 'k'                       ; up
    when 'j'                       ; down
    when ('1'..@options.size.to_s) ; select_n(char.to_i)
    when 'y', 'n'                  ; select_bool(char)
    when " ", "\r", "\n"           ; @answer = @active # <enter>
    when "\u0003"                  ; raise Interrupt   # Ctrl-c
    end
  when :esc
    case char
    when '[' ; @state = :esc_bracket
    else     ; raise Interrupt # unhandled escape sequence.
    end
  when :esc_bracket
    @state = :root
    case char
    when 'A' ; up
    when 'B' ; down
    else     ; raise Interrupt # unhandled escape sequence.
    end
  end
end