:: widerstand zwecklos ::
email jabber gpgkey

August 08, 2010

vi-like z-shell

Filed under: computer -- 21:16

I've recently started to use zsh's vi like keyboard mode. I used to say, that
this was pretty much unusable for me, even though I'm a long-time vi user.

Sometimes however, I was missing some of the command-mode (well actually in
vi-lingo, that's "normal mode"...) editing capabilities. So, I tried to use
zsh's vi emulation. The biggest drawback for me was, that - since I'm using
many many shells in parallel - I was easily losing track of what mode a shell
was in. Was it insert mode? Command mode? Did I enable the overwrite bit?

I knew zsh gained the ability of running a special zle widget when there is
a keymap change. That's pretty good for tracking the keyboard state. Good,
but not perfect. So I worked out a few issues I was having and ultimately
came up with the following: ft's example vi mode setup for zsh

I'm tracking the current keyboard state in `psvar[1]' and use that at the
start of my prompt definition (the `i', `c', `im' etc. strings in the
following screenshots). At first I'll load up an xterm that runs zsh without
any configuration (the `-f' option):

PS1='zsh%% ' xterm -e zsh -f

Then I'm loading my example setup, which drops me into the following session:

initial screenshot

See the little `i' at the beginning of the line? That tells me, I'm in insert
mode. There is a variable in my setup - `zle_default_mode' - which, if set to
`cmd' will cause zsh to put the line editor into command mode per default. I
used to think that was a very good idea. But I've since switched back to
insert mode per default. The code supports both modes as the default, though.

Like in vi, hitting `ESC' brings us to command mode. Or does it?
Well, in this setup it doesn't at least not per default. Why? First of all,
hitting Ctrl-d is easier then ESC (No, really. I'm doing that in vim and also
in emacs' viper mode). Also, in many terminals you'll experience a slight
delay between hitting ESC and the shell taking any action. That's due to how
the terminal interprets escape sequences; and you can't do much about it. If
you'd prefer to use `ESC' instead of `Ctrl-d' anyway, you can do that by
setting the variable `zle_use_ctrl_d' to `no'.

Here's how my prompt looks like after hitting `Ctrl-d'. The `c' tells me, my
shell's line editor is now in command mode:

entered command mode

Now let's go back to insert mode, but turn on the overwrite bit. That'll cause
the shell to overwrite an existing character if the cursor is on said
character and a new character is entered. Like in vi, hitting `R' will do that
for us (and the `r' will tell us of the mode change:

replace mode

Now we'll hit `Ctrl-d' and `i' to go back to insert mode:

back to insert mode

Now let's use a widget that opens a minibuffer; like one of the incremental
history searching widgets:

isearch backward

Since we moved from insert mode to a minibuffer, the prompt shows `im'. The
`m' signals the existence of a minibuffer.

Escaping from that minibuffer, by using `Ctrl-g' or `Ctrl-c' will also reset
the keyboard mode in the prompt:

escaping the minibuffer

The same works from command mode, too. Let's enter one of the vi-mode history
searching widgets:

search from command mode

Hitting `Ctrl-c' gets us back out of that again and the prompt will only
contain a `c' to tell us, the minibuffer is gone.

That's almost all. Almost.
Remember that I'm using `Ctrl-d' to switch from insert mode to command mode?
Sure you do. What about that key sending an `end-of-file' character, which
closes the shell quickly? Yeah. Bummer. But here's my way out. I've created
a widget which goes by the name `q'. So you can do `:q' like you would to
close vi. And to remind people about that, hitting `Ctrl-d' in command mode
will display the following:

:q reminder in command mode

There's one problem with this setup: When using the `execute-named-command'
widget, we cannot do any proper signaling at all. That's because you cannot
wrap that widget into your own. There's some nastiness in zsh's source code
that prevents that from working. See


for details. If you read that thread, you'll conclude that fixing this
requires quite some knowledge of the involved code. And that takes me out of
the picture. :)

Until that gets fixed, you'll be seeing an `i' while `execute-named-command'
is active. Deal with it. Because except for that, this seems to be working
pretty damn well.

Powered by zblog
valid css | valid xhtml | utf-8 encoded | best viewed with anybrowser