I want a hybrid mode where I can use all the default keymap emacs
key bindings by default, and still have the ability to change to vi-command
mode.
How do I set this up?
readlineshell
I want a hybrid mode where I can use all the default keymap emacs
key bindings by default, and still have the ability to change to vi-command
mode.
How do I set this up?
TL;DR
To view the default keybindings for (example) emacs
(the default), use:
INPUTRC=~/dev/null bash -c 'bind -pm emacs' | grep -vE '^#|: (do-lowercase-version|self-insert)$'
The vi
, vi-command
and vi-move
are one and the same keymap.
Both emacs-meta
and emacs-ctlx
are subset views into the emacs
keymap.
If you want further info on the vi
mode and maps, skip to the heading editing-mode vi
(the last one).
But wait! There's a fair bit of background info that may be needed though: eg, the difference between an editing-mode
and a keymap
.
Particularly useful is the concept of a hybrid emacs
keymap for inserting text and while still easily getting to vi-command
for making changes.
What is the difference between an editing-mode
and a keymap
?
There are only two editing-mode
s: emacs
(the default) and vi
.
The GNU Readline Library documentation says:
editing-mode The editing-mode variable controls which default set of key bindings is used. By default, Readline starts up in Emacs editing mode, where the keystrokes are most similar to Emacs. This variable can be set to either `emacs' or `vi'.
Note the difference between editing-mode
and keymap
: In editing-mode vi
the two (yes there's only two, read on) keymaps are swapped in and out to emulate the different modes of the vi
editor. ALL the emacs
ones operate at the same time in editing-mode emacs
(explained later).
So what does editing-mode
actually do? It just sets the active keymap upon shell startup to either emacs
or vi-insert
.
What are the unique keymaps?
Acceptable keymap names are emacs, emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move, vi-command, and vi-insert. vi is equivalent to vi-command; emacs is equivalent to emacs-standard.
While not documented, vi
/vi-command
and vi-move
keymaps are also equivalent:
+ravi@boxy:~$ diff <(bind -pm vi) <(bind -pm vi-move)
+ravi@boxy:~$
This leaves us with: emacs
, emacs-meta
, emacs-ctlx
, vi
, and vi-insert
as unique keymaps to explain. Differentiating the keymaps is probably best done by inspecting them...
What are the keymaps default bindings?
To view the default keybindings for (example) emacs (the default), use:
INPUTRC=~/dev/null bash -c 'bind -pm emacs' | grep -v '^#
You can replace emacs
with any other keymap name in the example above.
There are many lines saying self-insert
or do-lowercase-version
which aren't very useful, so to remove them:
INPUTRC=~/dev/null bash -c 'bind -pm emacs' | grep -vE '^#|: (do-lowercase-version|self-insert)$' | sort
What is the difference between the various emacs
keymaps?
TL;DR: They are different views on a single set of mappings applied to editing-mode emacs
.
If you the output of the second command into the files called emacs-standard
, emacs-meta
, emacs-ctlx
, vi-command
, and vi-insert
for their corresponding keymap
s, you can find out that:
There are NO commands mapped in emacs-meta
and emacs-ctlx
which don't also appear in emacs-standard
:
$ comm -13 <(sed -r 's/.*: (\S+)/\1/' emacs-standard|sort) <(sed -r 's/.*: (\S+)/\1/' emacs-ctlx|sort)
$ comm -13 <(sed -r 's/.*: (\S+)/\1/' emacs-standard|sort) <(sed -r 's/.*: (\S+)/\1/' emacs-meta|sort)
$
So emacs
/emacs-standard
is a behaviourally functional superset of both emacs-ctlx
and emacs-meta
This means that:
keymap emacs
"\eg": glob-expand-word
"\C-x\C-r": re-read-init-file
Is functionally equivalent to:
keymap emacs-meta
"g": glob-expand-word
keymap emacs-ctlx
"\C-r": re-read-init-file
You might argue that the second form is easier to read.
Inserting text: emacs
vs vi-insert
There are 28 commands in emacs-standard
not in vi-insert
+ravi@boxy:~/lib/readline$ comm -12 vi-insert emacs-standard |wc -l
28
+ravi@boxy:~/lib/readline$
emacs
/emacs-standard
is basically a superset of vi-insert
. So for typing text, it's best to use the emacs-standard
keymap over vi-insert
as long as you can easily switch between emacs
and vi-command
.
The only additional bindings in vi-insert
not in emacs-standard
are:
+ravi@boxy:~/lib/readline$ comm -23 vi-insert emacs-standard
"\C-d": vi-eof-maybe
"\C-n": menu-complete
"\C-p": menu-complete-backward
"\e": vi-movement-mode
The first 3 of these four conflict with emacs
bindings:
"\C-d": delete-char
"\C-n": next-history
"\C-p": previous-history
which I resolved as follows:
set keymap emacs
"\e": "kj" # see https://unix.stackexchange.com/questions/303631/how-can-i-setup-a-hybrid-readline-with-emacs-insert-mode-and-vi-command-mode
"\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
editing-mode vi
As we saw above, vi
, vi-command
and vi-move
are one and the same keymap:
+ravi@boxy:~$ diff <(bind -pm vi) <(bind -pm vi-move)
+ravi@boxy:~$
Note that's a total of just two distinct maps which are associated by default with editing-mode vi
.
When in editing-mode vi
, the keymap
s in use are vi
/vi-command
/vi-move
and vi-insert
(the starting keymap). Only one of these two maps is active at a time.
editing-mode vi
does nothing more than set a default keymap when the shell starts, labelled vi-insert
. Again, tthere is only one keymap active at a time. This vi-insert
keymap maps most keys to self-insert
so when you press the plastic button on your keyboard, the symbol printed on it appears on your screen.
The vi-insert
keymap allows itself to be swapped to the text-manipulating keymap called vi-command
/vi
/vi-move
by using vi-movement-mode
command, bound to the ESC key by default in the vi-insert
keymap.
Actually, even the emacs
keymap can set the vi
-like text manipulation keymap active by using the vi-movement-mode
command, as in the hybrid solution mentioned above.
Or in easier language...
By default, press ESC to change to the vi-command
keymap when the vi-insert
keymap is active.
The vi-command
keymap uses standard, single keypresses like a, b and c to move and interact with text, just like the vi
editor's default or command mode. There are generally no Ctrl+key combinations. You can't insert text in this mode; the letter keys are mapped to editing/moving commands. For typing text, you switch to the vi-insert
keymap (example: press i for "Insert").
Entering text is done using the the vi-insert
keymap, which is active when the shell starts if you have editing-mode vi
in your .inputrc
file. Swap to the vi-insert
keymap by pressing i for "insert" while in vi-command
(or in numerous other ways for those initiated into vi
).
Unless you know the vi
editor, you'll probably find vi-command
keys very hard to use at first, but if you get good at it, you can edit text like a long-bearded wizard.
I don't think you're doing anything wrong, you just need to tell Terminal.app to use the "Option" key as "Meta" instead. Just go to the preferences for Terminal (⌘+,), then Profiles, then the Keyboard tab for your default profile and check "Use Option as Meta key."
Otherwise, you're getting the default macOS behavior (in most keymaps) where Option + key produces special characters, just like Option+f yields the small letter "f" with hook from your screenshot (complete reference here).
In iTerm 2, I believe the left Option key already does the sensible thing by default, but just in case, here's how to change that:
As far as remapping Option to Escape, if that's a thing you still wanted to do, that can be accomplished from the Keyboard prefpane in System Preferences, by clicking the "Modifier Keys…" button near the bottom of the window.
Then remap it to whatever you please (from the available choices):
I hit up this preference pane in about the first five minutes of using a new Mac, just so I can remap Caps Lock to Control, since the Control key on a Mac keyboard is in an awkward spot.
Best Answer
I read @Tom Hale's answers here and here.
I think instead of moving
vi-insert
bindings toemacs
, the better approach is to moveemacs
bindings tovi-insert
. The reason is becausevi-command
has bindings that switch tovi-insert
and it's hard to emulate the functionality to make it switch toemacs
mode.For example, the
A
command invi-command
defaults tovi-append-eol
(appending at the end of the line and switch tovi-insert
).You can't make
A
switch toemacs
mode because it is bound to a function and not a macro.For example, this wouldn't work
Nor this, using @Tom Hale's answer
You could do this:
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 toemacs
. 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 tovi-insert
.So we want to set
vi-insert
bindings that are unique toemacs
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
The reason why the
| cat
is there is explained hereThe
-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
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 tovi-insert
which already has thevi-insert
binding. If you want theemacs
one (on the left), delete thevi-insert
one.If there is a unique binding on the right column (
vi-insert
), such asdelete 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 tovi-insert
.Here is my
.inputrc
with theemacs
bindings I selected added tovi-insert
.I decided not to use the
"kj"
to switch tovi-command
in @Tom Hale's answer because it can be done with"\ee"
which takes you fromemacs
tovi-insert
then another"\e"
which takes you fromvi-insert
tovi-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 useEnter
forvi-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 thevi-eof-maybe
binding invi-insert
mode with thedelete-char
binding.I kept
"\C-n": menu-complete
instead of"\C-n": next-history
because I could just use down arrow fornext-history
. This means to delete both bindings becausevi-insert
already has themenu-complete
binding.I kept
"\C-p": menu-complete-backward
instead of"\C-p": previous-history
because I could press up arrow forprevious-history
. This means to delete both bindings becausevi-insert
already has themenu-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 thevi-insert
one. This means to delete both bindings becausevi-insert
already has thevi-unix-word-rubout
binding.I kept
"\e": vi-movement-mode
. This means to delete this binding becausevi-insert
already has thevi-movement-mode
binding.UPDATE
I think I made it a little better. For a while, I turned back on the
"jk"
mooshing to switch tovi command
because pressing"\e"
to switch tovi-command
had a delay since a lot ofemacs
commands moved over tovi-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 fromvi-insert
tovi-command
because I don't see a need. So it has the effect where if you press"\e"
invi-insert
and wait, you will go tovi-command
.To get from
vi-command
tovi-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 2vi
modes.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
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 emacsunix-word-rubout
to fix this.To rebind
"\C-w"
, you might need to unbind defaults.If you would like to unbind all defaults:
Not sure if it matters, but I am on macOS so there are other default bindings I remove.
Then in your
.inputrc
: