Ben D
Ben D

Reputation: 475

Capture arrow key events in Ruby script

I'm writing a small script in Ruby that reads input from the command line.

I would like to catch the up , down , left, right arrow signal. I want to do something like in the Terminal. (when you hit up-arrow you have the previous command ...) How can I do that?

Upvotes: 3

Views: 2703

Answers (2)

egwspiti
egwspiti

Reputation: 987

Starting with ruby 1.9.3, io/console is shipped with ruby. One can use its #raw to achieve unbuffered input:

http://ruby-doc.org/stdlib-2.2.2/libdoc/io/console/rdoc/IO.html#method-i-raw

However, arrow keys are represented by a sequence of characters, not a single character. This sequence always starts with "\e" but unfortunately there isn't any end-of-sequence marker.

Something like this could be used to read arrow keys:

require 'io/console'
require 'timeout'

def readkey
  c = ''
  result = ''
  $stdin.raw do |stdin|
    c = stdin.getc
    result << c
    if c == "\e"
      begin
        while (c = Timeout::timeout(0.0001) { stdin.getc })
          result << c
        end
      rescue Timeout::Error
        # no action required
      end
    end
  end
  result
end

puts readkey.inspect #=> outputs "\e[D" if left arrow is pressed

Upvotes: 0

Casper
Casper

Reputation: 34308

To do completely unbuffered input you can use something like termios. However you will have to interpret arrow key sequences manually.

If you can live with a middle layer for history completion I suggest using GNU readline, like mentioned previously, or the RawLine library by H3RALD:

http://www.h3rald.com/rawline/
http://www.h3rald.com/articles/real-world-rawline-usage/

Example of unbuffered input with termios:

require 'rubygems'
require 'termios'

def with_unbuffered_input
  old_attrs = Termios.tcgetattr(STDOUT)

  new_attrs = old_attrs.dup

  new_attrs.lflag &= ~Termios::ECHO
  new_attrs.lflag &= ~Termios::ICANON

  Termios::tcsetattr(STDOUT, Termios::TCSANOW, new_attrs)

  yield
ensure
  Termios::tcsetattr(STDOUT, Termios::TCSANOW, old_attrs)
end

with_unbuffered_input do
  10.times {
    c = STDIN.getc
    puts "Got #{c}"
  }
end

Upvotes: 2

Related Questions