You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
blogcontent/blog/content/posts/edit-command-line-in-zsh.md

4.4 KiB

title date
Edit Command Line in Zsh 2022-07-10T00:25:21-07:00

While reading through my dotfiles, I found some configuration1 that didn't seem to be working - it claimed that <ESC>,v would allow editing of the current line in vim, but that didn't seem to work. I guess I'd copied that from some other configuration and lost patience with trying to get it working, or that it relied on some other configuration option which had been broken2. I dug in to find out more. (This article was invaluable!)

Intention

First, let's understand what that snippet is trying to do. The ZLE (Zsh Line Editor) is a tool3 that allows for:

  • the definition of various commands (called "widgets") for editing text.
  • the binding of those widgets (or built-in or imported ones) to keys within named "keymaps". Commands can switch between keymaps.

Three standard keymaps in ZLE are emacs, vicmd, and viins. In the viins (vi-insert) keymap, the <ESC> key is associated with a widget that switches to the vicmd keymap:

$ bindkey -M viins '^['
"^[" vi-cmd-mode

(The character string '^[' represents the single keypress <ESC>)

The line bindkey -M vicmd v edit-command-line means "Within the keymap vicmd, bind the widget edit-command-line to the key v". The earlier commands (autoload edit-command-line; zle -N edit-command-line) deal with making that widget available to ZLE - I guess it's not available by default?

So, taken as a whole, this configuration should make the key-sequence <ESC>,v translate to "enter vicmd mode, then execute edit-command-line". But that didn't seem to be the case.

Investigation and resolution

I quickly noticed that my default keymap was not viins but emacs:

$ bindkey -lL main
bindkey -A emacs main

Quick fix, right? Add a line bindkey -v4 to my dotfile, and done?

Well, sort-of. This change did allow the edit-command-line to fire as expected, allowing the current line to be edited in vi; but, after "writing" the command and returning to the command line, I was not able to delete any character of the written command. Initially I thought this was because I was still in vicmd mode (and created a workaround custom widget here), but that turned out to be an incorrect assumption. After looking around a little more, I found this SO answer which suggested changing the binding for '^?' (that is - <BACKSPACE>) from vi-backward-delete-char to backward-delete-char. I'm not sure why, though - backspaces still work fine on the terminal before entering vicmd mode, which lends further credence to my suspicion that the ZLE is not the entirety of the terminal, but merely a tool within it.

I've been making a lot of use of some Emacs-mode shortcuts:

  • Ctrl-A => beginning of line
  • Ctrl-E => end of line
  • Ctrl-U => clear everything

So I added these to my setup with the following commands:

bindkey -M viins '^A' beginning-of-line
bindkey -M viins '^E' end-of-line
bindkey -M viins '^U' kill-whole-line

  1. Still in Github until I fully migrate to my self-hosted Gitea instance. I'm cautious of a circular dependency here - Gitea would need to be up-and-available to source dotfiles, but dotfiles would be referenced as part of the setup process for hosts (including the one that runs the Gitea instance).

  2. An idea - regression testing for dotfiles? Don't tempt me...

  3. The article says that the ZLE is the command prompt, which...seems unlikely to me? I would think that the ZLE is a part of the command prompt, but not all of it? Although the article contains a lot of useful information and insight, it also has some rather loose and imprecise statements, so I'm not sure how much to trust this.

  4. bindkey -v is an alias for bindkey -A viins main - in ZLE, you don't set a keymap as active, instead you set a keymap as an alias for main, and I think that's beautiful.