3.2 Key Bindings

Viper lets you define hot keys, i.e., you can associate keyboard keys such as F1, Help, PgDn, etc., with Emacs Lisp functions (that may already exist or that you will write). Each key has a “preferred form” in Emacs. For instance, the Up key’s preferred form is [up], the Help key’s preferred form is [help], and the Undo key has the preferred form [f14]. You can find out the preferred form of a key by typing M-x describe-key-briefly and then typing the key you want to know about.

Under the X Window System, every keyboard key emits its preferred form, so you can just type

(global-set-key [f11] 'calendar)                        ; L1, Stop
(global-set-key [f14] 'undo)                            ; L4, Undo

to bind L1 (a key that exists on some SUN workstations) so it will invoke the Emacs Calendar and to bind L4 so it will undo changes. However, on a dumb terminal or in an Xterm window, even the standard arrow keys may not emit the right signals for Emacs to understand. To let Emacs know about those keys, you will have to find out which key sequences they emit by typing C-q and then the key (you should switch to Emacs state first). Then you can bind those sequences to their preferred forms using input-decode-map as follows:

(cond ((string= (getenv "TERM") "xterm")
(define-key input-decode-map "\e[192z" [f11])    ; L1
(define-key input-decode-map "\e[195z" [f14])    ; L4, Undo

The above illustrates how to do this for Xterm. On VT100, you would have to replace "xterm" with "vt100" and also change the key sequences (the same key may emit different sequences on different types of terminals).

The above keys are global, so they are overwritten by the local maps defined by the major modes and by Viper itself. Therefore, if you wish to change a binding set by a major mode or by Viper, read this.

Viper users who wish to specify their own key bindings should be concerned only with the following three keymaps: viper-vi-global-user-map for Vi state commands, viper-insert-global-user-map for Insert state commands, and viper-emacs-global-user-map for Emacs state commands (note: customized bindings for Emacs state made to viper-emacs-global-user-map are not inherited by Insert state).

For more information on Viper keymaps, see the header of the file viper.el. If you wish to change a Viper binding, you can use the define-key command, to modify viper-vi-global-user-map, viper-insert-global-user-map, and viper-emacs-global-user-map, as explained below. Each of these key maps affects the corresponding Viper state. The keymap viper-insert-global-user-map also affects Viper’s Replace state.

If you want to bind a key, say C-v, to the function that scrolls page down and to make 0 display information on the current buffer, putting this in your Viper customization file will do the trick in Vi state:

(define-key viper-vi-global-user-map "\C-v" 'scroll-down)

To set a key globally,

(define-key viper-emacs-global-user-map "\C-c m" 'smail)
(define-key viper-vi-global-user-map "0" 'viper-info-on-file)

Note, however, that this binding may be overwritten by other keymaps, since the global keymap has the lowest priority. To make sure that nothing will override a binding in Emacs state, you can write this:

(define-key viper-emacs-global-user-map "\C-c m" 'smail)

To customize the binding for C-h in Insert state:

(define-key viper-insert-global-user-map "\C-h"

Each Emacs command key calls some Lisp function. If you have enabled the Help, (see Rudimentary Changes) C-h k will show you the function for each specific key; C-h b will show all bindings, and C-h m will provide information on the major mode in effect. If Help is not enabled, you can still get help in Vi state by prefixing the above commands with \, e.g., \ C-h k (or you can use the Help menu in the menu bar, if Emacs runs under X).

Viper users can also change bindings on a per major mode basis. As with global bindings, this can be done separately for each of the three main Viper states. To this end, Viper provides the function viper-modify-major-mode.

To modify keys in Emacs state for my-favorite-major-mode, the user needs to create a sparse keymap, say, my-fancy-map, bind whatever keys necessary in that keymap, and put

(viper-modify-major-mode 'dired-mode 'emacs-state my-fancy-map)

in your Viper customization file. To do the same in Vi and Insert states, you should use vi-state and insert-state. Changes in Insert state are also in effect in Replace state. For instance, suppose that the user wants to use dd in Vi state under Dired mode to delete files, u to unmark files, etc. The following code in the Viper customization file will then do the job:

(setq my-dired-modifier-map (make-sparse-keymap))
(define-key my-dired-modifier-map "dd" 'dired-flag-file-deletion)
(define-key my-dired-modifier-map "u" 'dired-unmark)
(viper-modify-major-mode 'dired-mode 'vi-state my-dired-modifier-map)

A Vi purist may want to modify Emacs state under Dired mode so that k, l, etc., will move around in directory buffers, as in Vi. Although this is not recommended, as these keys are bound to useful Dired functions, the trick can be accomplished via the following code:

(setq my-dired-vi-purist-map (make-sparse-keymap))
(define-key my-dired-vi-purist-map "k" 'viper-previous-line)
(define-key my-dired-vi-purist-map "l" 'viper-forward-char)
(viper-modify-major-mode 'dired-mode
                         'emacs-state my-dired-vi-purist-map)

Yet another way to customize key bindings in a major mode is to edit the list viper-major-mode-modifier-list using the customization widget. (This variable is in the Viper-misc customization group.) The elements of this list are triples of the form: (major-mode viper-state keymap), where the keymap contains bindings that are supposed to be active in the given major mode and the given viper-state.

Effects similar to key binding changes can be achieved by defining Vi keyboard macros using the Ex commands :map and :map!. The difference is that multi-key Vi macros do not override the keys they are bound to, unless these keys are typed in quick succession. So, with macros, one can use the normal keys alongside with the macros. If per-mode modifications are needed, the user can try both ways and see which one is more convenient. See Vi Macros, for details.

Note: in major modes that come up in Emacs state by default, the aforesaid modifications may not take place immediately (but only after the buffer switches to some other Viper state and then back to Emacs state). To avoid this, one should add viper-change-state-to-emacs to an appropriate hook of that major mode. (Check the function viper-set-hooks in viper.el for examples.) However, if you did not set viper-always to nil, chances are that you won’t need to perform the above procedure, because Viper will take care of most useful defaults.

Finally, Viper has a facility that lets the user define per-buffer bindings, i.e., bindings that are in effect in some specific buffers only. Unlike per-mode bindings described above, per-buffer bindings can be defined based on considerations other than the major mode. This is done via the function viper-add-local-keys, which lets one specify bindings that should be in effect in the current buffer only and for a specific Viper state. For instance,

(viper-add-local-keys 'vi-state '(("ZZ" . TeX-command-master)
                                 ("ZQ" . viper-save-kill-buffer)))

redefines ZZ to invoke TeX-command-master in vi-state and ZQ to save-then-kill the current buffer. These bindings take effect only in the buffer where this command is executed. The typical use of this function is to execute the above expression from within a function that is included in a hook to some major mode. For instance, the above expression could be called from a function, my-tex-init, which may be added to tex-mode-hook as follows:

(add-hook 'tex-mode-hook 'my-tex-init)

When TeX mode starts, the hook is executed and the above Lisp expression is evaluated. Then, the bindings for ZZ and ZQ are changed in Vi command mode for all buffers in TeX mode.

Another useful application is to bind ZZ to send-mail in the Mail mode buffers (the specifics of this depend on which mail package you are using, rmail, mh-e, vm, etc. For instance, here is how to do this for mh-e, the Emacs interface to MH:

(defun mh-add-vi-keys ()
  "Set up ZZ for MH-e and XMH."
  (viper-add-local-keys 'vi-state '(("ZZ" . mh-send-letter))))
(add-hook 'mh-letter-mode-hook 'mh-add-vi-keys)

You can also use viper-add-local-keys to set per buffer bindings in Insert state and Emacs state by passing as a parameter the symbols insert-state and emacs-state, respectively. As with global bindings, customized local bindings done to Emacs state are not inherited by Insert state.

On rare occasions, local keys may be added by mistake. Usually this is done indirectly, by invoking a major mode that adds local keys (e.g., shell-mode redefines RET). In such a case, exiting the wrong major mode won’t rid you from unwanted local keys, since these keys are local to Viper state and the current buffer, not to the major mode. In such situations, the remedy is to type M-x viper-zap-local-keys.

So much about Viper-specific bindings. See Customization in The GNU Emacs Manual, and the Emacs quick reference card for the general info on key bindings in Emacs.