2

I want to allow the user to redefine key bindings for the interactive commands in a major mode. I tried putting them in strings, but I can't get the customized variable value in my mode-map. Here's what I tried:

In the init file:

(require 'myfeature)

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(myfeature-kb-configure "C-c C-u"))

In myfeature.el:

(defcustom myfeature-kb-configure "C-c C-p"
  "Plop."
  :type 'string
  :group 'myfeature-mode-keyboard-shortcuts)

(defvar myfeature-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map (kbd myfeature-kb-configure) 'myfeature-conf) ; => "C-c C-p"
     map)
   "Keymap for `myfeature-mode'.")

;;;###autoload
(define-derived-mode myfeature-mode prog-mode "myfeature" 
"Myfeature mode"
  (kill-all-local-variables)
  (message "key: %s" myfeature-kb-configure) ; => "C-c C-u"
  (use-local-map myfeature-mode-map)
  (run-hooks 'change-major-mode-after-body-hook 'after-change-major-mode-hook))

(provide 'myfeature)

Apparently, I'm defining myfeature-mode-map too early for the mode to pick up the "customized" value ; I get the old value, C-c C-p in the mode-map, because anywhere else in the file (including in the mode definition) I get the customized value. How can I ensure that the customized value gets read every time?

Nota bene:

Ideally, I would present to the user a menu with a fixed list (choice) of my commands and a field next to it to input proper key bindings (not string), already properly filled with default values so he/she would understand right away what to do, but I don't know how to do that, and found no example of such a marvel.

  • 1
    This is a nice idea, but you might want to consider that some Emacs users don't like the customize interface, and prefer to set up their config with setq and define-key etc. This will be harder to manage if your mode explicitly over-rides keybindings they have set elsewhere. – Tyler Aug 1 '17 at 14:08
  • @Tyler Good point. Another problem to solve :) – yPhil Aug 1 '17 at 14:26
3

If you want Customize (or custom-set-variables or customize-set-variable) to not only change the value of the option but also define that new key value in the keymap then you need to use a :set function in your defcustom:

(defcustom myfeature-kb-configure "C-c C-p"
  "Plop."
  :type 'string
  :set (lambda (sym defs)
         (custom-set-default sym defs)
         (define-key myfeature-mode-map (kbd myfeature-kb-configure) 'myfeature-conf))
  :group 'myfeature-mode-keyboard-shortcuts)

But note that in that :set function I hard-coded the command to bind to the key.


A different approach, which I've taken in Icicles, is this:

  1. Provide an option (actually, I use several, for different keymaps and different kinds/groups of keys) whose value is a list of keys and commands to bind to them.

  2. In the mode command (icicle-mode), use the option to bind the keys to the commands. In my case, the mode is a minor mode, but the idea is the same.

The relevant code for defining the option(s) is in icicles-opt.el. For example:

(defcustom icicle-top-level-key-bindings
  `(((kbd "<pause>")  icicle-switch-to/from-minibuffer  t)
    ((kbd "C-c `")    icicle-search-generic             t)
    ((kbd "C-c $")    icicle-search-word                t)
    ...)
  "Doc..."
  :type  '(repeat icicle-key-definition)
  :set (lambda (sym defs)
         (custom-set-default sym defs)
         (when (boundp 'icicle-mode-map) ; Avoid error on initialization.
           (icicle-bind-top-level-commands defs)))
  :initialize #'custom-initialize-default
  :group 'Icicles-Key-Bindings)

The code that binds the keys in the keymap(s) is in icicles-mode.el. The mode command (icicle-mode) calls the function icicle-bind-top-level-commands, which binds the keys to the commands. (The same function is used in the defcustom :set function.)

Actually, such key-binding options allow for a third field, besides the key and the command to bind to it: a Boolean expression that is used to control whether the binding should be made. In the case shown above, the expression was always t, meaning bind the key unconditionally.

And the key part of the binding description can, instead of a key, be a command to remap to the command to be bound. Here's an example where the binding description says to remap command bmkp-bookmark-set-confirm-overwrite to command icicle-bookmark-cmd, if the former command is defined:

(bmkp-bookmark-set-confirm-overwrite             ; Command to remap
 icicle-bookmark-cmd                             ; Command to use in the mode
 (fboundp 'bmkp-bookmark-set-confirm-overwrite)) ; Condition for remapping

The :type for such an key-bindings option is (repeat icicle-key-definition). That type widget is defined this way:

(define-widget 'icicle-key-definition 'lazy
  "Key definition type for Icicle mode keys.
A list of three components: KEY, COMMAND, CONDITION, that represents
an `icicle-mode-map' binding of COMMAND according to KEY, if CONDITION
evaluates to non-nil.

KEY is either a key sequence (string or vector) or a command.
COMMAND is a command.
CONDITION is a sexp.

If KEY is a command, then the binding represented is its remapping to
COMMAND."
  :indent 1 :offset 0 :tag ""
  :type
  '(list
    (choice
     (key-sequence :tag "Key" :value [ignore])
     (restricted-sexp :tag "Command to remap" :match-alternatives (symbolp) :value ignore))
    (restricted-sexp :tag "Command" :match-alternatives (symbolp) :value ignore)
    (sexp :tag "Condition")))

That uses symbolp instead of commandp, in case the library defining the command is not loaded.

  • Wow, thorough answer :) Actually, I only needed to set my key not in the table defun, but to at them to it later, in the major mode defun : (define-key myfeature-mode-map (kbd myfeature-kb-configure) 'myfeature-configure) – yPhil Aug 1 '17 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.