22

I want to switch to vi editing mode in a readline environment. But I don't want to use 'set -o vi'. I want to temporarily switch using a keyboard shortcut. The man page says I can do this with M-C-j. But that doesn't work for me.

I'm using Ubuntu and an xterm. Doesn't work under gnome-terminal either.

5 Answers5

15

I'd confirm that the keyboard mapping Meta+Control+j is in fact correct on your system. You can use this command to list all the keybinds for the various modes of Bash. On my system there wasn't a keybinding either.

$ bind -P| grep edit
edit-and-execute-command can be found on "\C-x\C-e".
emacs-editing-mode is not bound to any keys
vi-editing-mode is not bound to any keys

You can do the following so that when you type Esc+e it will toggle between the 2 modes.

$ set -o emacs
$ bind '"\ee": vi-editing-mode'
$ set -o vi
$ bind '"\ee": emacs-editing-mode'

The bind command now shows this:

in vi mode

$ bind -P |grep edit
edit-and-execute-command is not bound to any keys
emacs-editing-mode can be found on "\ee".
vi-editing-mode is not bound to any keys

in emacs mode

$ bind -P |grep edit
edit-and-execute-command can be found on "\C-x\C-e".
emacs-editing-mode is not bound to any keys
vi-editing-mode can be found on "\ee".

Now you can use Esc+e to toggle between the 2 different modes.

slm
  • 369,824
  • Note that you have to be quick when typing ESC E. If you pause you will go from vi-insert to vi-command mode, or just cancell the current vi command. – spelufo Feb 20 '15 at 03:05
8

Bash explicitly disables this and a few other Readline shortcuts. See the initialize_readline() function in the bash source code (http://www.catonmat.net/download/bashline.c):

   /* In Bash, the user can switch editing modes with "set -o [vi emacs]",
      so it is not necessary to allow C-M-j for context switching.  Turn
      off this occasionally confusing behaviour. */
   rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap);
   rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap);
#if defined (VI_MODE)
  rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap);
#endif

I don't seem to be able to override this behavior using the Readline configuration file (.inputrc).

6

Here's what I ended up using for my ~/.inputrc, based on slm's answer.

set show-mode-in-prompt on

set keymap emacs
"\ea": vi-editing-mode

set keymap vi-command
"k": history-search-backward
"j": history-search-forward
"z": emacs-editing-mode
"\ea": emacs-editing-mode

set keymap vi-insert
"\ea": emacs-editing-mode
"\C-l": clear-screen
"\C-e": end-of-line
"\C-k": kill-line

set editing-mode vi

I tried the $if mode= syntax, but I think that is resolved statically (one time, when reading the file), so it doesn't work as I expected. So we need to switch to each keymap and modify its key bindings, even if previously set on another keymaps. At the end I say which mode I want to start with.

spelufo
  • 477
3

I tried to have emacs-styled mappings being used in vi mode. I ended up with:

set keymap vi-command
"k": history-search-backward
"j": history-search-forward

set keymap vi-insert
"\C-A": beginning-of-line
"\C-B": backward-char
"\C-D": delete-char
"\C-E": end-of-line
"\C-F": forward-char
"\C-K": kill-line
"\C-L": clear-screen
"\C-N": next-history
"\C-P": previous-history
"\C-O": operate-and-get-next

# Enable Readline not waiting for additional input when a key is pressed.
# Needed for the mappings below.
set keyseq-timeout 0

# `yank-last-arg` does not work exactly as in emacs mode
"\e.": yank-last-arg
"\e\177": backward-kill-word
"\e0": digit-argument
"\e1": digit-argument
"\e2": digit-argument
"\e3": digit-argument
"\e4": digit-argument
"\e5": digit-argument
"\e6": digit-argument
"\e7": digit-argument
"\e8": digit-argument
"\e9": digit-argument
"\eb": backward-word
"\ec": capitalize-word
"\ed": kill-word
"\ef": forward-word
"\el": downcase-word
"\en": non-incremental-forward-search-history
"\ep": non-incremental-reverse-search-history
"\et": transpose-words
"\eu": upcase-word
"\ey": yank-pop

# some other useful mappings

"\e/": complete-filename
"\ek": kill-whole-line
"\eo": "\C-v\C-j"
# quickly switch to "normal" mode
"\C-[": vi-movement-mode
# perserve the currently editing line so that we can 
# do something else before restoring it.
"\eg": insert-comment
"\er": "\C-R#\C-A\C-D\C-E"

set editing-mode vi

It is helpful to read the man page for readline and the READLINE section on the bash man page.

0

This should work in any bash to switch to vi-mode:

Ctrl-a, Ctrl-k, set -o vi, Ctrl-y

Jump to start. Cut to end-of-line. Switch to vi-mode. Yank the line back.

Not the perfect simple hotkey but it will work at any bash without setting anything up first. For me this is also easy to memorize because I use ctrl-a/e and ctrl-k/u all the time. You can also hold down ctrl and type ak.

Related: In particular, I like the W/B vi hotkeys to move between words. This is different from w/b (or emacs m-f/m-b) which treat the following as 4 words not 1: "a/b/c/d" or "a,b:c-d". It becomes super-fast to move through a huge command line. f' is also useful to jump to the next quote (or any char) and ;/, move between matches.

Interestingly, the Ctrl-y emacs hotkey still works in vi-input-mode.