Sunday, October 10, 2010

GNU Readline

GNU readline is a library providing in most simplified explanation functionality for interactive input line editing. On top of that it provides features like:

  • editing modes (emacs or vi mode)
  • history support
  • configurable keybindings
  • macros

There are several applications out there leveraging GNU readline like bash (probably best known shell), python interpreter and others.

Readline is configured in configuration file which location is determined by environment variable 'INPUTRC'. If such variable is not set '~/.inputrc' is assumed with fallback to '/etc/inputrc'. In archlinux in '/etc/profile', variable is set 'INPUTRC=/etc/inputrc' and then in the '/etc/inputrc' along with some global useful settings there is an include for '~/.inputrc'.

# no terminal bell
set bell-style none

# vi editing mode
# insert mode
set editing-mode vi
set keymap vi-insert

"\C-l": clear-screen
"\C-p": history-search-backward
"\C-n": history-search-forward
"\C-o": menu-complete
"\C-k": menu-complete-backward
# forward \C-M (CR - carriage return) to \C-J (LF - line feed)
"\r": "\n"

$if bash
    "\C-x\C-x": "\C-[0isudo \C-[0"
    "\C-x\C-s": "\C-[ddiscreen\n"
    "\C-x\C-t": "\C-[dditmux new -s ''\C-[i"

# command mode
set keymap vi-command

"\C-p": history-search-backward
"\C-n": history-search-forward
# forward \C-M (CR - carriage return) to \C-J (LF - line feed)
"\r": "\n"

# effectively before 'accept-line' switch to 'insert' mode
# this can be helpfull in case of vi mode visualization
"\n": "i\n"

$if bash
    "\C-x\C-x": "0isudo \C-[0"
    "\C-x\C-s": "ddiscreen\n"
    "\C-x\C-t": "dditmux new -s ''\C-[i"

# show completion matches immediately
set show-all-if-ambiguous on

# show completion matches only if there is no partial completion
set show-all-if-unmodified on

# do not insert the completion text if it is already present
set skip-completed-text on

By default 'C-s' is bound to the 'forward-search-history'. However if the terminal flow control is enabled 'C-s' and 'C-q' is interpreted by terminal with meaning 'C-s' = XOFF (transmission OFF) and 'C-q' = XON (transmission ON). Flow control can be disabled for example by '$ stty -ixon'.

Generally it is useful to specify key bindings in 'inputrc' file (as in one above) because such settings are common for all readline applications (you can specify settings selectively for some terminal, keymap or application). In bash it is possible by 'bind' command to override readline key bindings but what is more useful you can setup key bindings to specific bash readline extending functions.

excerpt from ~/.bashrc
bind -m vi-insert '"\C-e":shell-expand-line'
bind -m vi-command '"\C-e":shell-expand-line'
bind -m vi-insert '"\ee":history-and-alias-expand-line'
bind -m vi-insert '"\eE":history-and-alias-expand-line'
bind -m vi-command '"\ee":history-and-alias-expand-line'
bind -m vi-command '"\eE":history-and-alias-expand-line'
bind -m vi-insert '"\ei":complete-filename'
bind -m vi-insert '"\eI":complete-filename'
bind -m vi-insert '"\e\t":dynamic-complete-history'
bind -m vi-insert '"\eq":dabbrev-expand'

On top of that there is a feature of a 'bind' command that you can hook shell function to some key sequence. Inside of that function you can work with 'READLINE_LINE' and 'READLINE_POINT' variables. They are quite self-explanatory and changes to them will be reflected in readline. With a bit of trickery you can create hook which will be called before 'accept-line'.

bind -m vi-insert -x '"\C-x\C-p":readline_before_accept_line'
bind -m vi-insert '"\C-x\C-n":accept-line'
bind -m vi-insert '"\n":"\C-x\C-p\C-x\C-n"'

readline_before_accept_line() {
    printf '\n...%s:%s\n' "$READLINE_POINT" "$READLINE_LINE"

Related resources: