POE::Wheel::ReadLine - non-blocking Term::ReadLine for POE
#!perl use warnings; use strict; use POE qw(Wheel::ReadLine); POE::Session->create( inline_states=> { _start => \&setup_console, got_user_input => \&handle_user_input, } ); POE::Kernel->run(); exit; sub handle_user_input { my ($input, $exception) = @_[ARG0, ARG1]; my $console = $_[HEAP]{console}; unless (defined $input) { $console->put("$exception caught. B'bye!"); $_[KERNEL]->signal($_[KERNEL], "UIDESTROY"); $console->write_history("./test_history"); return; } $console->put(" You entered: $input"); $console->addhistory($input); $console->get("Go: "); } sub setup_console { $_[HEAP]{console} = POE::Wheel::ReadLine->new( InputEvent => 'got_user_input' ); $_[HEAP]{console}->read_history("./test_history"); $_[HEAP]{console}->clear(); $_[HEAP]{console}->put( "Enter some text.", "Ctrl+C or Ctrl+D exits." ); $_[HEAP]{console}->get("Go: "); }
POE::Wheel::ReadLine is a non-blocking form of Term::ReadLine that's compatible with POE. It uses Term::Cap to interact with the terminal display and Term::ReadKey to interact with the keyboard.
POE::Wheel::ReadLine handles almost all common input editing keys. It provides an input history list. It has both vi and emacs modes. It supports incremental input search. It's fully customizable, and it's compatible with standard readline(3) implementions such as Term::ReadLine::Gnu.
POE::Wheel::ReadLine is configured by placing commands in an "inputrc"
initialization file. The file's name is taken from the INPUTRC
environment variable, or ~/.inputrc by default. POE::Wheel::ReadLine
will read the inputrc file and configure itself according to the
commands and variables therein. See readline(3) for details about
inputrc files.
The default editing mode will be emacs-style, although this can be configured by setting the 'editing-mode' variable within an inputrc file. If all else fails, POE::Wheel::ReadLine will determine the user's favorite editor by examining the EDITOR environment variable.
Most of POE::Wheel::ReadLine's interaction is through its constructor, new().
new() creates and returns a new POE::Wheel::ReadLine object. Be sure to instantiate only one, as multiple console readers would conflict.
InputEvent
names the event that will indicate a new line of console
input. See /PUBLIC EVENTS for more details.
PutMode
controls how output is displayed when put() is called
during user input.
When set to "immediate", put() pre-empts the user immediately. The input prompt and user's input to date are redisplayed after put() is done.
The "after" PutMode
tells put() to wait until after the user enters
or cancels her input.
Finally, "idle" will allow put() to pre-empt user input if the user
stops typing for /IdleTime
seconds. This mode behaves like "after"
if the user can't stop typing long enough. This is
POE::Wheel::ReadLine's default mode.
IdleTime
tells POE::Wheel::ReadLine how long the keyboard must be
idle before put()
becomes immediate or buffered text is flushed to
the display. It is only meaningful when /InputMode is "idle".
IdleTime
defaults to 2 seconds.
AppName
registers an application name which is used to retrieve
application-specific key bindings from the inputrc file. The default
AppName
is "poe-readline".
# If using POE::Wheel::ReadLine, set # the key mapping to emacs mode and # trigger debugging output on a certain # key sequence. $if poe-readline set keymap emacs Control-xP: poe-wheel-debug $endif
POE::Wheel::ReadLine supports an input history, with searching.
add_history() accepts a list of lines to add to the input history.
Generally it's called with a single line: the last line of input
received from the terminal. The /SYNOPSIS
shows add_history() in
action.
get_history() returns a list containing POE::Wheel::ReadLine's current input history. It may not contain everything entered into the wheel
TODO - Example.
write_history() writes the current input history to a file. It accepts one optional parameter: the name of the file where the input history will be written. write_history() will write to ~/.history if no file name is specified.
Returns true on success, or false if not.
The /SYNOPSIS shows an example of write_history() and the corresponding read_history().
read_history(FILENAME, START, END) reads a previously saved input history from a named file, or from ~/.history if no file name is specified. It may also read a subset of the history file if it's given optional START and END parameters. The file will be read from the beginning if START is omitted or zero. It will be read to the end if END is omitted or earlier than START.
Returns true on success, or false if not.
The /SYNOPSIS shows an example of read_history() and the corresponding write_history().
Read the first ten history lines:
$_[HEAP]{console}->read_history("filename", 0, 9);
history_truncate_file() truncates a history file to a certain number of lines. It accepts two parameters: the name of the file to truncate, and the maximum number of history lines to leave in the file. The history file will be cleared entirely if the line count is zero or omitted.
The file to be truncated defaults to ~/.history. So calling history_truncate_file() with no parameters clears ~/.history.
Returns true on success, or false if not.
Note that history_trucate_file() removes the earliest lines from the file. The later lines remain intact since they were the ones most recently entered.
Keep ~/.history down to a manageable 100 lines:
$_[HEAP]{console}->history_truncate_file(undef, 100);
bind_key(KEYSTROKE, FUNCTION) binds a FUNCTION to a named KEYSTROKE sequence. The keystroke sequence can be in any of the forms defined within readline(3). The function should either be a pre-defined name, such as "self-insert" or a function reference. The binding is made in the current keymap. Use the rl_set_keymap() method to change keymaps, if desired.
add_defun(NAME, FUNCTION) defines a new global FUNCTION, giving it a specific NAME. The function may then be bound to kestrokes by that NAME.
Clears the terminal.
Returns what POE::Wheel::ReadLine thinks are the current dimensions of the terminal. Returns a list of two values: the number of columns and number of rows, respectively.
sub some_event_handler { my ($columns, $rows) = $_[HEAP]{console}->terminal_size; $_[HEAP]{console}->put( "Terminal columns: $columns", "Terminal rows: $rows", ); }
get() causes POE::Wheel::ReadLine to display a prompt and then wait for input. Input is not noticed unless get() has enabled the wheel's internal I/O watcher.
After get() is called, the next line of input or exception on the
console will trigger an InputEvent
with the appropriate parameters.
POE::Wheel::ReadLine will then enter an inactive state until get() is
called again.
See the /SYNOPSIS for sample usage.
put() accepts a list of lines to put on the terminal. POE::Wheel::ReadLine is line-based. See POE::Wheel::Curses for more funky display options.
Please do not use print() with POE::Wheel::ReadLine. print()
invariably gets the newline wrong, leaving an application's output to
stairstep down the terminal. Also, put() understands when a user is
entering text, and PutMode
may be used to avoid interrupting the
user.
attribs() returns a reference to a hash of readline options. The returned hash may be used to query or modify POE::Wheel::ReadLine's behavior.
option(NAME) returns a specific member of the hash returned by attribs(). It's a more convenient way to query POE::Wheel::ReadLine options.
POE::Wheel::ReadLine emits only a single event.
InputEvent
names the event that will be emitted upon any kind of
complete terminal input. Every InputEvent
handler receives three
parameters:
$_[ARG0]
contains a line of input. It may be an empty string if
the user entered an empty line. An undefined $_[ARG0]
indicates
some exception such as end-of-input or the fact that the user canceled
their input or pressed C-c (^C).
$_[ARG1]
describes an exception, if one occurred. It may contain
one of the following strings:
Finally, $_[ARG2]
contains the ID for the POE::Wheel::ReadLine
object that sent the InputEvent
.
POE::Wheel::ReadLine allows custom functions to be bound to keystrokes. The function must be made visible to the wheel before it can be bound. To register a function, use POE::Wheel::ReadLine's add_defun() method:
POE::Wheel::ReadLine->add_defun('reverse-line', \&reverse_line);
When adding a new defun, an optional third parameter may be provided which is a key sequence to bind to. This should be in the same format as that understood by the inputrc parsing.
Bound functions receive three parameters: A reference to the wheel object itself, the key sequence that triggered the function (in printable form), and the raw key sequence. The bound function is expected to dig into the POE::Wheel::ReadLine data members to do its work and display the new line contents itself.
This is less than ideal, and it may change in the future.
An application may modify POE::Wheel::ReadLine's "completion_function" in order to customize how input should be completed. The new completion function must accept three scalar parameters: the word being completed, the entire input text, and the position within the input text of the word being completed.
The completion function should return a list of possible matches. For example:
my $attribs = $wheel->attribs(); $attribs->{completion_function} = sub { my ($text, $line, $start) = @_; return qw(a list of candidates to complete); }
This is the only form of completion currently supported.
Although POE::Wheel::ReadLine is modeled after the readline(3) library, there are some areas which have not been implemented. The only option settings which have effect in this implementation are: bell-style, editing-mode, isearch-terminators, comment-begin, print-completions-horizontally, show-all-if-ambiguous and completion_function.
The function 'tab-insert' is not implemented, nor are tabs displayed properly.
POE::Wheel describes the basic operations of all wheels in more depth. You need to know this.
readline(3), Term::Cap, Term::ReadKey.
The SEE ALSO section in POE contains a table of contents covering the entire POE distribution.
Term::Visual is an alternative to POE::Wheel::ReadLine. It provides scrollback and a status bar in addition to editable user input. Term::Visual supports POE despite the lack of "POE" in its name.
POE::Wheel::ReadLine has some known issues:
Non-blocking input with Term::ReadKey does not work with Perl 5.8.0, especially on Linux systems for some reason. Upgrading Perl will fix things. If you can't upgrade Perl, consider alternative input methods, such as Term::Visual.
http://rt.cpan.org/Ticket/Display.html?id=4524 and related tickets explain the issue in detail. If you suspect your system is one where Term::ReadKey fails, you can run this test program to be sure.
#!/usr/bin/perl use Term::ReadKey; print "Press 'q' to quit this test.\n"; ReadMode 5; # Turns off controls keys while (1) { while (not defined ($key = ReadKey(-1))) { print "Didn't get a key. Sleeping 1 second.\015\012"; sleep (1); } print "Got key: $key\015\012"; ($key eq 'q') and last; } ReadMode 0; # Reset tty mode before exiting exit;
Dissociating the input and display cursors introduced a lot of code. Much of this code was thrown in hastily, and things can probably be done with less work.
TODO: Apply some thought to what's already been done.
TODO: Ensure that the screen updates as quickly as possible, especially on slow systems. Do little or no calculation during displaying; either put it all before or after the display. Do it consistently for each handled keystroke, so that certain pairs of editing commands don't have extra perceived latency.
Input editing is not kept on one line. If it wraps, and a terminal cannot wrap back through a line division, the cursor will become lost.
Unicode support. I feel real bad about throwing away native representation of all the 8th-bit-set characters. I also have no idea how to do this, and I don't have a system to test this. Patches are very much welcome.
Q: Why do I lose my prompt every time I send output to the screen?
A: You probably are using print or printf to write screen output. ReadLine doesn't track STDOUT itself, so it doesn't know when to refresh the prompt after you do this. Use ReadLine's put() method to write lines to the console.
Q: None of the editing keystrokes work. Ctrl-C displays "^c" rather than generating an interrupt. The arrow keys don't scroll through my input history. It's generally a bad experience.
A: You're probably a vi/vim user. In the absence of a ~/.inputrc file, POE::Wheel::ReadLine checks your EDITOR environment variable for clues about your editing preference. If it sees /vi/ in there, it starts in vi mode. You can override this by creating a ~/.inputrc file containing the line "set editing-mode emacs", or adding that line to your existing ~/.inputrc. While you're in there, you should totally get acquainted with all the other cool stuff you can do with .inputrc files.
Q: Why doesn't POE::Wheel::ReadLine work on Windows? Term::ReadLine does.
A: POE::Wheel::ReadLine requires select(), because that's what POE uses by default to detect keystrokes without blocking. About half the flavors of Perl on Windows implement select() in terms of the same function in the WinSock library, which limits select() to working only with sockets. Your console isn't a socket, so select() doesn't work with your version of Perl on Windows.
Really good workarounds are possible but don't exist as of this writing. They involve writing a special POE::Loop for Windows that either uses a Win32-specific module for better multiplexing, that polls for input, or that uses blocking I/O watchers in separate threads.
POE::Wheel::ReadLine was originally written by Rocco Caputo.
Nick Williams virtually rewrote it to support a larger subset of GNU readline.
Please see POE for more information about other authors and contributors.