class TTY::Prompt::Reader
A class responsible for reading character input from STDIN
Used internally to provide key and line reading functionality
@api private
Constants
- BACKSPACE
- CARRIAGE_RETURN
Key
codes- DELETE
- InputInterrupt
Raised when the user hits the interrupt key(Control-C)
@api public
- NEWLINE
Attributes
Public Class Methods
Initialize a Reader
@param [IO] input
the input stream
@param [IO] output
the output stream
@param [Hash] options @option options [Symbol] :interrupt
handling of Ctrl+C key out of :signal, :exit, :noop
@option options [Boolean] :track_history
disable line history tracking, true by default
@api public
# File lib/tty/prompt/reader.rb, line 58 def initialize(input = $stdin, output = $stdout, options = {}) @input = input @output = output @interrupt = options.fetch(:interrupt) { :error } @env = options.fetch(:env) { ENV } @track_history = options.fetch(:track_history) { true } @console = select_console(input) @history = History.new do |h| h.duplicates = false h.exclude = proc { |line| line.strip == '' } end @stop = false # gathering input subscribe(self) end
Public Instance Methods
# File lib/tty/prompt/reader.rb, line 277 def add_to_history(line) @history.push(line) end
Get input code points
@param [Hash] options @param [Array] codes
@return [Array]
@api private
# File lib/tty/prompt/reader.rb, line 135 def get_codes(options = {}, codes = []) opts = { echo: true, raw: false }.merge(options) char = console.get_char(opts) return if char.nil? codes << char.ord condition = proc { |escape| (codes - escape).empty? || (escape - codes).empty? && !(64..126).include?(codes.last) } while console.escape_codes.any?(&condition) get_codes(options, codes) end codes end
# File lib/tty/prompt/reader.rb, line 285 def history_next @history.next @history.get end
# File lib/tty/prompt/reader.rb, line 281 def history_next? @history.next? end
# File lib/tty/prompt/reader.rb, line 294 def history_previous line = @history.get @history.previous line end
# File lib/tty/prompt/reader.rb, line 290 def history_previous? @history.previous? end
Inspect class name and public attributes @return [String]
@api public
# File lib/tty/prompt/reader.rb, line 304 def inspect "#<#{self.class}: @input=#{input}, @output=#{output}>" end
Capture Ctrl+d and Ctrl+z key events
@api private
# File lib/tty/prompt/reader.rb, line 272 def keyctrl_d(*) @stop = true end
Read a keypress including invisible multibyte codes and return a character as a string. Nothing is echoed to the console. This call will block for a single keypress, but will not wait for Enter to be pressed.
@param [Hash] options @option options [Boolean] echo
whether to echo chars back or not, defaults to false
@option options [Boolean] raw
whenther raw mode enabled, defaults to true
@return [String]
@api public
# File lib/tty/prompt/reader.rb, line 116 def read_keypress(options = {}) opts = { echo: false, raw: true }.merge(options) codes = unbufferred { get_codes(opts) } char = codes ? codes.pack('U*') : nil trigger_key_event(char) if char handle_interrupt if char == console.keys[:ctrl_c] char end
Get a single line from STDIN. Each key pressed is echoed back to the shell. The input terminates when enter or return key is pressed.
@param [String] prompt
the prompt to display before input
@param [Boolean] echo
if true echo back characters, output nothing otherwise
@return [String]
@api public
# File lib/tty/prompt/reader.rb, line 166 def read_line(*args) options = args.last.respond_to?(:to_hash) ? args.pop : {} prompt = args.empty? ? '' : args.pop opts = { echo: true, raw: true }.merge(options) line = Line.new('') ctrls = console.keys.keys.grep(/ctrl/) clear_line = "\e[2K\e[1G" while (codes = unbufferred { get_codes(opts) }) && (code = codes[0]) char = codes.pack('U*') trigger_key_event(char) if console.keys[:backspace] == char || BACKSPACE == code next if line.start? line.left line.delete elsif console.keys[:delete] == char || DELETE == code line.delete elsif [console.keys[:ctrl_d], console.keys[:ctrl_z]].include?(char) break elsif console.keys[:ctrl_c] == char handle_interrupt elsif ctrls.include?(console.keys.key(char)) # skip elsif console.keys[:up] == char next unless history_previous? line.replace(history_previous) elsif console.keys[:down] == char line.replace(history_next? ? history_next : '') elsif console.keys[:left] == char line.left elsif console.keys[:right] == char line.right else if opts[:raw] && code == CARRIAGE_RETURN char = "\n" line.move_to_end end line.insert(char) end if opts[:raw] && opts[:echo] output.print(clear_line) output.print(prompt + line.to_s) if char == "\n" line.move_to_start elsif !line.end? output.print("\e[#{line.size - line.cursor}D") end end break if (code == CARRIAGE_RETURN || code == NEWLINE) if (console.keys[:backspace] == char || BACKSPACE == code) && opts[:echo] if opts[:raw] output.print("\e[1X") unless line.start? else output.print(?\s + (line.start? ? '' : ?\b)) end end end add_to_history(line.to_s.rstrip) if track_history? line.to_s end
Read multiple lines and return them in an array. Skip empty lines in the returned lines array. The input gathering is terminated by Ctrl+d or Ctrl+z.
@param [String] prompt
the prompt displayed before the input
@yield [String] line
@return [Array]
@api public
# File lib/tty/prompt/reader.rb, line 244 def read_multiline(prompt = '') @stop = false lines = [] loop do line = read_line(prompt) break if !line || line == '' next if line !~ /\S/ && !@stop if block_given? yield(line) unless line.to_s.empty? else lines << line unless line.to_s.empty? end break if @stop end lines end
Select appropriate console
@api private
# File lib/tty/prompt/reader.rb, line 77 def select_console(input) if windows? && !env['TTY_TEST'] WinConsole.new(input) else Console.new(input) end end
Expose event broadcasting
@api public
# File lib/tty/prompt/reader.rb, line 265 def trigger(event, *args) publish(event, *args) end
Get input in unbuffered mode.
@example
unbufferred do ... end
@api public
# File lib/tty/prompt/reader.rb, line 93 def unbufferred(&block) bufferring = output.sync # Immediately flush output output.sync = true block[] if block_given? ensure output.sync = bufferring end
Private Instance Methods
Handle input interrupt based on provided value
@api private
# File lib/tty/prompt/reader.rb, line 327 def handle_interrupt case @interrupt when :signal Process.kill('SIGINT', Process.pid) when :exit exit(130) when Proc @interrupt.call when :noop return else raise InputInterrupt end end
Publish event
@param [String] char
the key pressed
@return [nil]
@api private
# File lib/tty/prompt/reader.rb, line 318 def trigger_key_event(char) event = KeyEvent.from(console.keys, char) trigger(:"key#{event.key.name}", event) if event.trigger? trigger(:keypress, event) end
Check if Windowz mode
@return [Boolean]
@api public
# File lib/tty/prompt/reader.rb, line 347 def windows? ::File::ALT_SEPARATOR == '\\' end