ohmyzsh: vi-mode plugin must be loaded first or it wipes out custom keybindings in other plugins

It would appear that if the vi-mode plugin appears in the list of plugins after another plugin which defines a keybinding using bindkey, that keybinding is removed. Specifically, the keybindings I’ve observed this with are:

bindkey "^[[1;6D" insert-cycledleft
bindkey "^[[1;6C" insert-cycledright

(from https://github.com/mcornella/oh-my-zsh/commit/383ae2d4aa1c4fa90bf35cdd6e13598dacafa5b1).

This isn’t documented anywhere (I believe), and isn’t obvious. Perhaps there’s a way to always logically sort vi-mode to the head of the list of plugins, irrespective of where it’s defined? Or emit a warning if it isn’t first?

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Reactions: 1
  • Comments: 20 (15 by maintainers)

Most upvoted comments

A “keymap” is a named set of key bindings. zsh maintains a few of them to switch between, depending on what you’re doing.

I don’t think there’s an easy fix for this, because vi-mode and the other key binding stuff in OMZ grew kind of organically. Best you can do in the short term is have plugins that know they don’t want to be clobbered by vi-mode check the $plugins list to see if vi-mode is being loaded after them. We could supply a function in lib that made that easy for the plugins to do. (And it could be added to later if other plugins were created that switched or clobbered the keymap like vi-mode did and needed to be checked similarly.)

Some other plugins add key bindings; they’d probably want to check for it, too.

➜  .oh-my-zsh git:(master) grep -rl bindkey *
lib/completion.zsh
lib/key-bindings.zsh
plugins/colemak/colemak.plugin.zsh
plugins/dircycle/dircycle.plugin.zsh
plugins/dirhistory/dirhistory.plugin.zsh
plugins/history-substring-search/history-substring-search.zsh
plugins/jump/jump.plugin.zsh
plugins/per-directory-history/per-directory-history.plugin.zsh
plugins/per-directory-history/per-directory-history.zsh
plugins/safe-paste/safe-paste.plugin.zsh
plugins/sudo/sudo.plugin.zsh
plugins/vi-mode/vi-mode.plugin.zsh
themes/dieter.zsh-theme
➜  .oh-my-zsh git:(master)

Alternately, we could just put the check in vi-mode itself, and have it check for a list of plugins known to add key bindings, and issue a single warning.

Part of the issue may be that the bindkey -e and bindkey -v commands that lib/key-bindings.zsh and the vi-mode plugin use don’t just set up key bindings. They set up links between keymaps and change which one is linked to main and thus the default.

From the ZLE Builtins section of the manual:

bindkey’s options can be divided into three categories: keymap selection for the current command, operation selection, and others. The keymap selection options are:

-e

Selects keymap ‘emacs’ for any operations by the current command, and also links ‘emacs’ to ‘main’ so that it is selected by default the next time the editor starts. -v

Selects keymap ‘viins’ for any operations by the current command, and also links ‘viins’ to ‘main’ so that it is selected by default the next time the editor starts.

This means that lib files, user custom files, and plugins loaded before vi-mode are modifying the emacs keymap, and plugins loaded after vi-mode (and the rest of your .zshrc) are modifying the viins keymap, which is now the default. You can observe this by looking at bindkey -lL

# Normal:
➜  .oh-my-zsh git:(master) bindkey -lL | grep main
bindkey -A emacs main

# With vi-mode loaded:
➜  ~  bindkey -lL | grep main
bindkey -A viins main

And then you have to consider the specific key bindings that vi-mode makes inside the viins keymap.

If you want all the various files that set key bindings to play nicer with each other, you might add a $ZSH_INITIAL_KEYMAP option to let the user select whether bindkey -e or bindkey -v is done initially in lib/key-bindings.sh. But that begs the question of whether those various files would work well with the viins keymap, or if they’re just made to work with the current behavior of always starting with the emacs keymap. And it would impair the ability of the user to switch between the two at runtime. (Not a common case, surely, but it’s something that’s at least not actively interfered with now.) Most of the existing bindings look emacs-y.