How can I setup a hybrid readline with emacs insert mode and vi command mode?
I read @Tom Hale's answers here and here.
I think instead of moving vi-insert
bindings to emacs
, the better approach is to move emacs
bindings to vi-insert
. The reason is because vi-command
has bindings that switch to vi-insert
and it's hard to emulate the functionality to make it switch to emacs
mode.
For example, the A
command in vi-command
defaults to vi-append-eol
(appending at the end of the line and switch to vi-insert
).
You can't make A
switch to emacs
mode because it is bound to a function and not a macro.
For example, this wouldn't work
"A": vi-append-eol emacs-editing-mode
Nor this, using @Tom Hale's answer
"A": vi-append-eol "\ee"
You could do this:
"A": "$a\ee"
But now that depends on the "a"
, vi-append-mode
command, which also needs to be rebound. "a"
could be move forward then "i"
. "i"
could just switch to emacs
. There's a whole chain of commands to translate into macros which is a pain to do.
So you are better off moving the emacs
bindings to vi-insert
.
So we want to set vi-insert
bindings that are unique to emacs
And we want to make a decision on which binding to use if they have different bindings for the same key sequence.
If they have the exact same binding, we ignore them.
This can be done with this command
comm -3\
<(INPUTRC=/dev/null bash -c 'bind -pm emacs' |
LC_ALL='C' grep -vE '^#|: (do-lowercase-version|self-insert)$' |
sort) \
<(INPUTRC=/dev/null bash -c 'bind -pm vi-insert' |
LC_ALL='C' grep -vE '^#|: (do-lowercase-version|self-insert)$' |
sort) | cat
The reason why the | cat
is there is explained here
The -3
throws away the "exact same bindings"
So you go through this list and look for bindings on the left column. For each binding on the left column:
If there is a duplicate binding for the same key sequence, such as
"\C-d": delete-char
"\C-d": vi-eof-maybe
Choose one of them. If you want the vi-insert
one (on the right), you can delete both lines, because we will be adding these bindings to vi-insert
which already has the vi-insert
binding. If you want the emacs
one (on the left), delete the vi-insert
one.
If there is a unique binding on the right column (vi-insert
), such as
"\e": vi-movement-mode
delete it because `vi-insert already has it.
The rest of the bindings will be on the left column (emacs
). Leave these alone because we will add these to vi-insert
.
Here is my .inputrc
with the emacs
bindings I selected added to vi-insert
.
I decided not to use the "kj"
to switch to vi-command
in @Tom Hale's answer because it can be done with "\ee"
which takes you from emacs
to vi-insert
then another "\e"
which takes you from vi-insert
to vi-command
. There are actually words other than blackjack containing kj and words with jk (mostly place names)
I kept "\C-d": delete-char
and threw away "\C-d": vi-eof-maybe
. Because I could just use Enter
for vi-eof-maybe
and I don't want to accidentally quit readline by pressing "\C-d"
. This means to delete the "\C-d": vi-eof-maybe
binding because we are overriding the vi-eof-maybe
binding in vi-insert
mode with the delete-char
binding.
I kept "\C-n": menu-complete
instead of "\C-n": next-history
because I could just use down arrow for next-history
. This means to delete both bindings because vi-insert
already has the menu-complete
binding.
I kept "\C-p": menu-complete-backward
instead of "\C-p": previous-history
because I could press up arrow for previous-history
. This means to delete both bindings because vi-insert
already has the menu-complete-backward
binding.
I kept "\C-w": vi-unix-word-rubout
instead of "\C-w": unix-word-rubout
. I don't know what's the difference. I just stuck with the vi-insert
one. This means to delete both bindings because vi-insert
already has the vi-unix-word-rubout
binding.
I kept "\e": vi-movement-mode
. This means to delete this binding because vi-insert
already has the vi-movement-mode
binding.
set editing-mode vi
set keymap emacs
"\ee": vi-editing-mode
set keymap vi-command
"\ee": emacs-editing-mode
# key bindings to get out of vi-editing-mode
set keymap vi-insert
"\ee": emacs-editing-mode
# emacs keybindings in vi-insert mode
"\C-@": set-mark
"\C-]": character-search
"\C-_": undo
"\C-a": beginning-of-line
"\C-b": backward-char
"\C-d": delete-char
"\C-e": end-of-line
"\C-f": forward-char
"\C-g": abort
"\C-k": kill-line
"\C-l": clear-screen
"\C-o": operate-and-get-next
"\C-q": quoted-insert
"\C-x!": possible-command-completions
"\C-x$": possible-variable-completions
"\C-x(": start-kbd-macro
"\C-x)": end-kbd-macro
"\C-x*": glob-expand-word
"\C-x/": possible-filename-completions
"\C-x@": possible-hostname-completions
"\C-x\C-?": backward-kill-line
"\C-x\C-e": edit-and-execute-command
"\C-x\C-g": abort
"\C-x\C-r": re-read-init-file
"\C-x\C-u": undo
"\C-x\C-v": display-shell-version
"\C-x\C-x": exchange-point-and-mark
"\C-xe": call-last-kbd-macro
"\C-xg": glob-list-expansions
"\C-x~": possible-username-completions
"\e ": set-mark
"\e!": complete-command
"\e#": insert-comment
"\e$": complete-variable
"\e&": tilde-expand
"\e*": insert-completions
"\e-": digit-argument
"\e.": insert-last-argument
"\e.": yank-last-arg
"\e/": complete-filename
"\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
"\e<": beginning-of-history
"\e=": possible-completions
"\e>": end-of-history
"\e?": possible-completions
"\e@": complete-hostname
"\e\C-?": backward-kill-word
"\e\C-]": character-search-backward
"\e\C-e": shell-expand-line
"\e\C-g": abort
"\e\C-h": backward-kill-word
"\e\C-i": dynamic-complete-history
"\e\C-r": revert-line
"\e\C-y": yank-nth-arg
"\e\\": delete-horizontal-space
"\e\e": complete
"\e^": history-expand-line
"\e_": insert-last-argument
"\e_": yank-last-arg
"\eb": backward-word
"\ec": capitalize-word
"\ed": kill-word
"\ef": forward-word
"\eg": glob-complete-word
"\el": downcase-word
"\en": non-incremental-forward-search-history
"\ep": non-incremental-reverse-search-history
"\er": revert-line
"\et": transpose-words
"\eu": upcase-word
"\ey": yank-pop
"\e{": complete-into-braces
"\e~": complete-username
UPDATE
I think I made it a little better. For a while, I turned back on the "jk"
mooshing to switch to vi command
because pressing "\e"
to switch to vi-command
had a delay since a lot of emacs
commands moved over to vi-insert
uses "\e"
as a leader.
This shows the "jk"
mooshing commented out. I currently use "\ee"
to cycle modes. I didn't unbind the "\e"
to switch from vi-insert
to vi-command
because I don't see a need. So it has the effect where if you press "\e"
in vi-insert
and wait, you will go to vi-command
.
To get from vi-command
to vi-insert
, you'll just press one of the commands such as "A"
or "i"
so allowing cycling between 3 modes won't hurt because you can also just cycle between the 2 vi
modes.
set keymap emacs
"\ee": vi-editing-mode
set keymap vi-command
"\ee": emacs-editing-mode
# key bindings to get out of vi-editing-mode
set keymap vi-insert
# Choose one of these editor switching modes
#
# moosh jk to switch
#"\ee": emacs-editing-mode
#"\ejk": vi-movement-mode
#"\ekj": vi-movement-mode
#
# "\ee" to cycle
# can unmap "\e" to switch to vi-command but don't see a need
#"\e":
"\ee": vi-movement-mode
UPDATE
In vi-insert
, "\C-w"
is bound to `vi-unix-word-rubout, which stops at word boundaries or something. I didn't like that functionality.
For example, try this
$ cannot-delete'
# press left arrow to go back behind the single quote
# press \C-w in vi-insert to try to delete cannot-delete, it won't work
this bug report describes the problem, although I don't have a problem with the example provided.
So you can bind "\C-w"
to the emacs unix-word-rubout
to fix this.
To rebind "\C-w"
, you might need to unbind defaults.
# In .bashrc
stty werase undef
If you would like to unbind all defaults:
# In .inputrc
set bind-tty-special-chars off
Not sure if it matters, but I am on macOS so there are other default bindings I remove.
Then in your .inputrc
:
"\C-w": unix-word-rubout
The following .inputrc
lines allow Meta / Alt+E to switch between emacs
and vi-insert
modes.
Mooshing both j and k simultaneously will take you to vi-command
mode.
Note: The only English word with "kj" is "blackjack", no words contain "jk")
set show-mode-in-prompt on
set keymap emacs
"\ee": vi-editing-mode
"jk": "\eejk"
"kj": "\eejk"
set keymap vi-insert
"\ee": emacs-editing-mode
"jk": vi-movement-mode
"kj": vi-movement-mode
set keymap vi-command
"\ee": emacs-editing-mode
Note: In bash
v4.3.11(1), if you add a binding under keymap emacs
to vi-movement-mode
to try to switch straight to the vi-command
keymap, the prompt doesn't update if you have show-mode-in-prompt on
, hence this work-around is needed.
Interesting factoids:
There are only 4 bindings unique to vi-insert
mode, which can be easily added to emacs
mode:
"\C-d": vi-eof-maybe
"\C-n": menu-complete
"\C-p": menu-complete-backward
"\e": vi-movement-mode
However note that the following are the default emacs
bindings:
"\C-d": delete-char
"\C-n": next-history
"\C-p": previous-history
Which I resolved as follows:
set keymap emacs
"\e": "kj" # needs to be below "kj" mapping
"\C-d": delete-char # eof-maybe: ^D does nothing if there is text on the line
"\C-n": menu-complete
"\C-p": menu-complete-backward
"\C-y": previous-history # historY
"\e\C-y": previous-history
Old answer
This is how I did it before I could go directly from emacs
to vi-command
. It involves importing all the default emacs
commands into the vi-insert
keymap.
Get the default emacs-standard
and vi-insert
mappings:
INPUTRC=~/dev/null bash -c 'bind -pm emacs-standard' | grep -vE '^#|: (do-lowercase-version|self-insert)$' | sort > emacs-standard
INPUTRC=~/dev/null bash -c 'bind -pm vi-insert' | grep -vE '^#|: (do-lowercase-version|self-insert)$' | sort > vi-insert
Get only the additions from emacs-standard
:
comm -23 emacs-standard vi-insert > vi-insert-emacs-additions
Then, in your ~/.inputrc
, add the content of vi-insert-emacs-additions
under the lines:
(echo set editing-mode vi && echo set keymap vi-insert && cat vi-insert-emacs-additions) >> ~/.inputrc
For your convenience, on bash 4.3.11(1)-release, vi-insert-emacs-additions
contents are:
"\C-a": beginning-of-line "\C-b": backward-char "\C-]": character-search "\C-d": delete-char "\C-e": end-of-line "\C-f": forward-char "\C-g": abort "\C-k": kill-line "\C-l": clear-screen "\C-n": next-history "\C-o": operate-and-get-next "\C-p": previous-history "\C-q": quoted-insert "\C-@": set-mark "\C-_": undo "\C-x\C-?": backward-kill-line "\C-x\C-e": edit-and-execute-command "\C-x\C-g": abort "\C-x\C-r": re-read-init-file "\C-x\C-u": undo "\C-x\C-v": display-shell-version "\C-x\C-x": exchange-point-and-mark "\C-xe": call-last-kbd-macro "\C-x)": end-kbd-macro "\C-xg": glob-list-expansions "\C-x*": glob-expand-word "\C-x!": possible-command-completions "\C-x/": possible-filename-completions "\C-x@": possible-hostname-completions "\C-x~": possible-username-completions "\C-x$": possible-variable-completions "\C-x(": start-kbd-macro "\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 "\e<": beginning-of-history "\e\C-?": backward-kill-word "\ec": capitalize-word "\e\C-]": character-search-backward "\e\C-e": shell-expand-line "\e\C-g": abort "\e\C-h": backward-kill-word "\e\C-i": dynamic-complete-history "\e!": complete-command "\e/": complete-filename "\e@": complete-hostname "\e{": complete-into-braces "\e~": complete-username "\e$": complete-variable "\e\C-r": revert-line "\e\C-y": yank-nth-arg "\e\\": delete-horizontal-space "\e-": digit-argument "\ed": kill-word "\e\e": complete "\e>": end-of-history "\ef": forward-word "\eg": glob-complete-word "\e^": history-expand-line "\e#": insert-comment "\e*": insert-completions "\e_": insert-last-argument "\e.": insert-last-argument "\el": downcase-word "\en": non-incremental-forward-search-history "\ep": non-incremental-reverse-search-history "\e=": possible-completions "\e?": possible-completions "\er": revert-line "\e ": set-mark "\e&": tilde-expand "\et": transpose-words "\eu": upcase-word "\e_": yank-last-arg "\e.": yank-last-arg "\ey": yank-pop
Note: If you add a binding under keymap emacs
to vi-movement-mode
to try to switch straight to vi-command
mode, the prompt doesn't update if you have show-mode-in-prompt on
.
This is why the above solution adds emacs
bindings to vi-insert
. This makes for a longer .inputrc
, but is required for a complete solution.