bash/readline equivalent of escape-dot in vi-mode - bash

Having recently switched to vi-mode in bash, the one thing I miss is esc . to get the last argument of the last command.
I know about ctrl _, but I always end up hitting ctrl - instead.
Is there another vi-mode equivalent for this?

I believe the closest solution to what you want is this:
In your .bashrc, right after "set -o vi"...
set -o vi
bind -m vi-command ".":insert-last-argument
This tells your bash to invoke the "insert-last-argument" action when '.' is used in vi-command mode. This of course means that you lose the normal "." functionality of VI; but if you are like me, you'll prefer this.
Addendum:
You may also want Ctrl-A, Ctrl-E, Ctrl-W and Ctrl-L to work (those were the ones I was missing the most):
bind -m vi-command ".":insert-last-argument
bind -m vi-insert "\C-l.":clear-screen
bind -m vi-insert "\C-a.":beginning-of-line
bind -m vi-insert "\C-e.":end-of-line
bind -m vi-insert "\C-w.":backward-kill-word

You can also use the following to restore the emacs "escape-dot inserts last argument" behaviour in vi mode:
bindkey -v '\e.' insert-last-word

By altering or adding ~/.inputrc
To restore certain bash goodies in vi-mode, simply alter or add ~/.inputrc like this:
set completion-ignore-case on
set show-all-if-ambiguous on
set show-all-if-unmodified on
set editing-mode vi
set keymap vi-insert
$if mode=vi
"\C-a": beginning-of-line
"\C-e": end-of-line
"\C-l": clear-screen
"\C-n": next-history
"\C-p": previous-history
"\C-w": backward-kill-word
"\e.": yank-last-arg
"\e_": yank-last-arg
$endif
Here are more bindable readline bash commands.

I always used alt . to get the last argument of the last command.
Also, the !$ will give you the last argument of the last command executed. There are a bunch of cool things you can do with the exclamation point, just check out the man page for bash and search for History Expansion.

How about just using $_ bash variable?

I'm pretty sure you can still use the equivalent for vi mode, which should be "ESC + ."

Related

Why can't I set \C-e in inputrc to be end-of-line for vi-command keymap?

I have this in my .inputrc, but Control-e doesn't move to end of line in command mode. All other bindings work.
$if mode=vi
set show-mode-in-prompt on
set keymap vi-insert
"\C-e": end-of-line
"\C-a": beginning-of-line
set keymap vi-command
"\C-e": end-of-line
"\C-a": beginning-of-line
$endif
I can see it's taking:
$ bind -p | grep 'end-of-line'
"\C-e": end-of-line
"\eOF": end-of-line
"\e[F": end-of-line
And I can see nothing else is bound to \C-e:
$ bind -p | grep 'C-e'
"\C-e": end-of-line
"\C-x\C-e": shell-expand-line
If I set it to \C-l, it works. So, what's special about \C-e in readline's vi mode that I'm not able to override?
Only other clue is \C-e causes a terminal beep.
Nothing else is in my .inputrc
I just tried and it also does not work for me. But the bind command works fine:
bind -m vi-command ' "\C-e": end-of-line '
Seems like there's something weird in readline loading .inputrc. So as a workaround you can put the bind command in your bashrc files.
According to Chet Ramey via the bug-bash#gnu.org mailing list,
This has been there forever. The default readline
vi command-mode keymap has ^E bound to switch to emacs editing mode. Since
bash uses `set -o emacs' for that, the bash readline initialization code
unbinds the key sequence. It needs to make sure that the function it's
bound to is still rl_emacs_editing_mode.
He provides a patch, which is probably beyond the scope of a SO answer here, so I'll just link to it: http://lists.gnu.org/archive/html/bug-bash/2019-01/msg00217.html
#pynex's workaround works fine.

Using vi mode in readline/bash appears to break history-search

I'm very fond of using readline's history-search-forward and history-search-backward with bash. I have the following in my .inpurc:
# Scroll through matching history with up and down keys
"\e[A":history-search-backward
"\e[B":history-search-forward
and use the up and down keys to scroll through matching commands in my history.
However, when I enable vi-mode, it seems to stop the history search working. I have vi-mode configured thusly (also in .inputrc):
# Enable vi mode
set editing-mode vi
set keymap vi-command
# insert/command mode indicator:
set show-mode-in-prompt on
# Indicator formatting in prompt:
set vi-cmd-mode-string "\1\e[0;34m\2[\1\e[0m\2C\1\e[0;34m\2]\1\e[0m\2 "
set vi-ins-mode-string "\1\e[0;34m\2[\1\e[0m\2I\1\e[0;34m\2]\1\e[0m\2 "
When I remove the vi-mode related lines from my .inputrc, history search works fine. When I put them back, it breaks.
Is there a way to enable both features simultaneously?
I'm using GNU Bash 4.4.12 installed through homebrew on OSX Sierra.
Works for me in vi-insert mode:
set editing-mode vi
set keymap vi-insert
"\e[A":history-search-backward
"\e[B":history-search-forward
Or you can write this in the bashrc:
set -o vi
bind -m vi-insert '"\e[A":history-search-backward'
bind -m vi-insert '"\e[B":history-search-forward'

How to change shortcut for iTerm2 clear lines?

Ctrl + l is the default shortcut for clear lines in iTerm2, I want to change it to Cmd + l, but can't find this action:
BTW I'm using zsh.
Ctrl-L or "form feed" is part of the ANSI/VT100 protocol (http://wiki.bash-hackers.org/scripting/terminalcodes), it's not specific to iTerm2.
Your best best is to use Applescript to send Ctrl-L to the terminal when Cmd-L is pressed.
This is an zsh binding is not an iTerm binding. Ctrl+l is the default binding for the clear-screen widget in zsh. The fact that it also works in bash (and maybe other shells) is merely convention. In bash - or rather readline, bash's command line editor - it is the default binding for a command that is also named clear-screen.
Generally, you can change a key binding in zsh with the command bindkey KEYSEQUENCE WIDGET. Unfortunately not all modifiers might be supported by iTerm2 for use with the shell. You can test, whether it is supported by running cat -v and then pressing the desired key combination. If Cmd+l is supported, than the output shown should be more than just "l". If it something more or other than just "l", then you can use the output to bind it. For example if cat -v shows "^[l" than you can bind it with bindkey '^[l' clear-screen and if you want to remove the default binding, you can do so with bindkey -r '^l'.

What does "set keymap vi" do?

I wanted vim-like navigation for my terminal, so I added:
set editing-mode vi
set keymap vi
To my .inputrc file based on this. editing-mode vi adds vi navigation. What does keymap vi do, and why do I need it?
TL;DR
If you don't want to change/add bindings in the default keymaps, you don't need the line keymap vi.
What keymap vi does is state that any bindings listed after that point apply to that keymap (which is exactly the same keymap as vi-command and vi-move).
If you want to change the insertion keymap (eg to add a Ctrl-A binding to go the beginning of the line while you're typing), you'll need to do this below a keymap vi-insert line.
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-modes: 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 keymaps, 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 keymaps 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.
From man readline (my emphasis):
The set of legal keymap names is emacs, emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move, vi-command, and vi-insert. vi is equivalent to vi-command
Thus, in your inputrc you can specify different keybinds for the different modes, so, for example, in command mode you could yank the last argument from the previous command with Altp, but this keybind would have no effect in insert mode (in this simple setup anyway):
set editing-mode vi
set keymap vi-command
# these are for vi-command mode
"\e[A": history-search-backward
"\e[B": history-search-forward
"\ep": yank-last-arg
set keymap vi-insert
# these are for vi-insert mode
"\e[A": history-search-backward
"\e[B": history-search-forward
Control-l: clear-screen

Is there a way to switch Bash or zsh from Emacs mode to vi mode with a keystroke?

I'd like to be able to switch temporarily from emacs mode to vi mode, since vi mode is sometimes better, but I'm usually half-way through typing something before I realize I want to use vi mode.
I don't want to switch permanently to vi mode, because I normally prefer emacs mode on the command line, mostly because it's what I'm used to, and over the years many of the keystrokes have become second nature. (As an editor I generally use emacs in viper mode, so that I can use both vi and emacs keystrokes, since I found myself accidentally using them in vi all the time, and screwing things up, and because in some cases I find vi keystrokes more memorable and handy, and in other cases emacs.)
You can create a toggle since the key bindings are separate between vi mode and emacs mode.
$ set -o emacs
$ bind '"\ee": vi-editing-mode'
$ set -o vi
$ bind '"\ee": emacs-editing-mode'
Now Alt-e (or Esc e) will toggle between modes.
Add this somewhere in your definition for PS1 so you have an indicator in your prompt of which mode you're in. It won't show the change immediately when you toggle modes, but it will update when a new prompt is issued.
$(set -o | grep emacs.*on >/dev/null 2>&1 && echo E || echo V)
Aha! I looked at the readline source, and found out that you can do this:
"\M-v": vi-editing-mode
"\M-e": emacs-editing-mode
There doesn't appear to be a toggle, but that's probably good enough!
For posterity's sake, here's my original answer, which could be useful for people trying to do things for which there is no readline function.
Here's a way you could set it up, clearing the current command line in the process. Not what you want, I know, but maybe it'll help someone else who finds this question. In ~/.inputrc:
"\M-v": "\C-k\C-uset -o vi\C-j" # alt (meta)-v: set vi mode
"\M-e": "\C-k\C-uset -o vi\C-j" # alt (meta)-e: set emacs mode
or to toggle...this should work:
"\M-t": "\C-k\C-u[[ \"$SHELLOPTS\" =~ '\\bemacs\\b' ]] && set -o vi || set -o emacs\C-j"
These are essentially aliases, taken one step farther to map to keys in readline so that you don't have to type an alias name and hit enter.
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 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: 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, hence the above work-around is needed.
I finally found out how to toggle vi and emacs mode with a single key e.g. [alt]+[i] in zsh:
# in the .zshrc
# toggle vi and emacs mode
vi-mode() { set -o vi; }
emacs-mode() { set -o emacs; }
zle -N vi-mode
zle -N emacs-mode
bindkey '\ei' vi-mode # switch to vi "insert" mode
bindkey -M viins 'jk' vi-cmd-mode # (optionally) exit to vi "cmd" mode
bindkey -M viins '\ei' emacs-mode # switch to emacs mode

Resources