Skip to content

Instantly share code, notes, and snippets.

@jules2689
Last active May 23, 2017 02:30
Show Gist options
  • Save jules2689/c9b4216992d9c5b7e368cad54dc1c07d to your computer and use it in GitHub Desktop.
Save jules2689/c9b4216992d9c5b7e368cad54dc1c07d to your computer and use it in GitHub Desktop.

Usage:

puts "What is your favourite colour?"
InteractivePrompt.call(%w(red green blue yellow purple orange))

Renders this:

selector

class Cursor
CONTROL_SEQ = "\e[".freeze
class << self
def show
"#{CONTROL_SEQ}?25h"
end
def hide
"#{CONTROL_SEQ}?25l"
end
def move(dir, n = 1)
mappings = {
up: 'A',
down: 'B',
left: 'D',
right: 'C',
}
[CONTROL_SEQ, n.to_s, mappings[dir]].join
end
def next_line
move(:down, '') + CONTROL_SEQ + "1G"
end
def previous_line
move(:up, '') + CONTROL_SEQ + "1G"
end
def end_of_previous_line
previous_line + move(:right, "\033[")
end
def clear(n = 1)
n.times.reduce('') do |acc, idx|
mover = idx == n - 1 ? '' : move(:up)
clear = CONTROL_SEQ + '2K' + CONTROL_SEQ + "1G"
acc << clear + mover
end
end
end
end
require 'io/console'
require_relative 'cursor'
class InteractivePrompt
def self.call(options)
list = self.new(options)
options[list.call - 1]
end
def initialize(options)
@options = options
@active = 1
@marker = '>'
@answer = nil
end
def call
print(Cursor.hide)
while @answer.nil?
render_options
get_input
print(Cursor.clear(@options.size + 1))
print(Cursor.end_of_previous_line + "\n")
end
render_options
@answer
ensure
print(Cursor.show)
print(Cursor.end_of_previous_line + "\n")
end
private
def get_input
case read_char
when "\e[A", 'h' # up
@active = @active - 1 % @options.length
when "\e[B", 'j' # down
@active = @active + 1 % @options.length
when " ", "\r" # enter/select
@answer = @active
when "\u0003", "\e" # Control-C or escape
exit 0
end
end
# Will handle 2-3 character sequences like arrow keys and control-c
def read_char
STDIN.echo = false
STDIN.raw!
input = STDIN.getc.chr
return input unless input == "\e"
input << STDIN.read_nonblock(3) rescue nil
input << STDIN.read_nonblock(2) rescue nil
ensure
STDIN.echo = true
STDIN.cooked!
return input
end
def render_options
@options.each_with_index do |choice, index|
num = index + 1
message = " #{num.to_s}. #{choice}"
message = "\x1b[36> #{message.strip}\x1b[0" if num == @active
puts message
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment