A set of methods for processing keyboard input in character, line and multiline modes.
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
benchmarks
bin
examples
lib
spec
tasks
.gitignore
.rspec
.travis.yml
CHANGELOG.md
CODE_OF_CONDUCT.md
Gemfile
ISSUE_TEMPLATE.md
LICENSE.txt
PULL_REQUEST_TEMPLATE.md
README.md
Rakefile
appveyor.yml
tty-reader.gemspec

README.md

TTY::Reader Gitter

Gem Version Build Status Build status Maintainability Coverage Status Inline docs

A pure Ruby library that provides a set of methods for processing keyboard input in character, line and multiline modes. In addition it maintains history of entered input with an ability to recall and re-edit those inputs and register to listen for keystroke events.

TTY::Reader provides independent reader component for TTY toolkit.

Compatibility

The tty-reader is not compatible with the GNU Readline and doesn't aim to be. It originated from tty-prompt project to provide flexibility, independence from underlying operating system and Ruby like API interface for creating different prompts.

TTY::Reader forges its own path to provide features necessary for building line editing in terminal applications!

Features

  • Pure Ruby
  • Line editing
  • Reading single keypress
  • Reading multiline input
  • History management
  • Ability to register for keystroke events
  • No global state
  • Works on Linux, OS X, FreeBSD and Windows
  • Supports Ruby versions >= 2.0.0 & JRuby

Installation

Add this line to your application's Gemfile:

gem 'tty-reader'

And then execute:

$ bundle

Or install it yourself as:

$ gem install tty-reader

Usage

reader = TTY::Reader.new

API

2.1 read_keypress

To read a single key stroke from the user use read_char or read_keypress:

reader.read_char
reader.read_keypress

2.2 read_line

By default read_line works in raw mode which means it behaves like a line editor that allows you to edit each character, respond to control characters such as Control-A to Control-B or navigate through history.

For example, to read a single line terminated by a new line character use read_line like so:

reader.read_line

If you wish for the keystrokes to be interpreted by the terminal instead, use so called cooked mode by providing the :raw option set to false:

reader.read_line(raw: false)

Any non-interpreted characters received are written back to terminal, however you can stop this by using :echo option set to false:

reader.read_line(echo: false)

You can also provide a line prefix displayed before input by passing it as a first aargument:

reader.read_line(">> ")
# >> input goes here ...

2.3 read_multiline

By default read_multiline works in raw mode which means it behaves like a multiline editor that allows you to edit each character, respond to control characters such as Control-A to Control-B or navigate through history.

For example, to read more than one line terminated by Ctrl+d or Ctrl+z use read_multiline:

reader.read_multiline
# => [ "line1", "line2", ... ]

If you wish for the keystrokes to be interpreted by the terminal instead, use so called cooked mode by providing the :raw option set to false:

reader.read_line(raw: false)

You can also provide a linke prefix displayed before input by passing a string as a first argument:

reader.read_multiline(">> ")

2.4 on

You can register to listen on a key pressed events. This can be done by calling on with a event name:

reader.on(:keypress) { |event| .... }

The event object is yielded to a block whenever particular key event fires. The event has key and value methods. Further, the key responds to following messages:

  • name - the name of the event such as :up, :down, letter or digit
  • meta - true if event is non-standard key associated
  • shift - true if shift has been pressed with the key
  • ctrl - true if ctrl has been pressed with the key

For example, to add listen to vim like navigation keys, one would do the following:

reader.on(:keypress) do |event|
  if event.value == 'j'
    ...
  end

  if event.value == 'k'
    ...
  end
end

You can subscribe to more than one event:

prompt.on(:keypress) { |key| ... }
      .on(:keydown)  { |key| ... }

2.5 subscribe

You can subscribe any object to listen for the emitted key events using the subscribe message. The listener would need to implement a method for every event it wishes to receive.

For example, if a MyListener class wishes to only listen for keypress event:

class MyListener
  def keypress(event)
    ...
  end
end

Then subcribing is done:

reader.subscribe(MyListener.new)

Alternatively, subscribe allows you to listen to events only for the dueration of block execution like so:

reader.subscribe(MyListener) do
  ...
end

2.6 unsubscribe

You can unsubscribe any object from listening to the key events using the unsubscribe message:

reader.unsubscribe(my_listener)

2.7 trigger

The signature for triggering key events is trigger(event, args...). The first argument is a key event name followed by any number of actual values related to the event being triggered.

For example, to trigger :keydown event do:

reader.trigger(:keydown)

To add vim bindings for line editing you could discern between alphanumeric inputs like so:

reader.on(:keypress) do |event|
  if event.value == 'j'
    reader.trigger(:keydown)
  end
  if evevnt.value == 'k'
    reader.trigger(:keyup)
  end
end

2.8 supported events

The available key events for character input are:

  • :keypress
  • :keyenter
  • :keyreturn
  • :keytab
  • :keybackspace
  • :keyspace
  • :keyescape
  • :keydelete
  • :keyalpha
  • :keynum

The navigation relted key events are:

  • :keydown
  • :keyup
  • :keyleft
  • :keyright
  • :keyhome
  • :keyend
  • :keyclear

The specific ctrl key events:

  • :keyctrl_a
  • :keyctrl_b
  • ...
  • :keyctrl_z

The key events for functional keys f* are:

  • :keyf1
  • :keyf2
  • ...
  • :keyf24

3. Configuration

3.1. :interrupt

By default InputInterrupt error will be raised when the user hits the interrupt key(Control-C). However, you can customise this behaviour by passing the :interrupt option. The available options are:

  • :signal - sends interrupt signal
  • :exit - exists with status code
  • :noop - skips handler
  • custom proc

For example, to send interrupt signal do:

reader = TTY::Reader.new(interrupt: :signal)

3.2. :track_history

The read_line and read_multiline provide history buffer that tracks all the lines entered during TTY::Reader.new interactions. The history buffer provides previoius or next lines when user presses up/down arrows respectively. However, if you wish to disable this behaviour use :track_history option like so:

reader = TTY::Reader.new(track_history: false)

3.3. :history_cycle

This option determines whether the history buffer allows for infinite navigation. By default it is set to false. You can change this:

reader = TTY::Reader.new(history_cycle: true)

3.4. :history_duplicates

This option controls whether duplicate lines are stored in history. By default set to true. You can change this:

reader = TTY::Reader.new(history_duplicates: false)

3.5. :history_exclude

This option allows you to exclude lines from being stored in history. It accepts a Proc with a line as a first argument. By default it is set to exlude empty lines. To change this:

reader = TTY::Reader.new(history_exclude: ->(line) { ... })

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-reader. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

  1. Clone the project on GitHub
  2. Create a feature branch
  3. Submit a Pull Request

Important notes:

  • All new features must include test coverage. At a bare minimum, unit tests are required. It is preferred if you include acceptance tests as well.
  • The tests must be be idempotent. Any test run should produce the same result when run over and over.
  • All new features must include source code & readme documentation Any new method you add should include yarddoc style documentation with clearly specified parameter and return types.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the TTY::Reader project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Copyright

Copyright (c) 2017-2018 Piotr Murach. See LICENSE for further details.