TAGS :Viewed: 1 - Published at: a few seconds ago

[ Define key in prefix keymap for a particular mode ]

I have all my keybindings in a separate minor mode - firstly, to be sure that keybindings that I consider mandatory wouldn't be overridden by some major mode; and secondly, to be able to disable all of them in one command in case I need too.

Up until now I have been extending prefix keys like so:

(define-key my-minor-mode-map (kbd "M-g f") 'my-goto-file-func)

But then I realized that there is such thing as prefix keymap, and for M-g key it is goto-map. So I'd rather extend this keymap instead of hardcoding the prefix key, so it would be like:

(define-key goto-map (kbd "f") 'my-goto-file-func)

But in this case my minor mode has nothing to do with it, so I lose all my advantages of using it.

One way would be to determine which key is bound to that particular prefix keymap and then use that key in the bindings. But it has a drawback - prefix key wouldn't be remapped for my minor mode if I remap it after loading my minor mode.

So are there any better ways to do that?

Answer 1

Extending keys the way you did is perfectly fine because it keeps complexity to a minimum. So while it is certainly possible to modify a keymap in your minor mode, it is not necessarily better.

In order to use a modified keymap in your minor mode without overriding the original one, simply set the original as your parent, e.g.

(defvar my-goto-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map goto-map)

You can then use a modified version in your minor mode:

(define-key my-goto-map "f" 'find-file)
(define-key my-mode-map (kbd "M-g") my-goto-map)

But the original goto-map is still alive and kicking, and modifying it will reflect in your my-goto-map:

(define-key goto-map "i" 'info)

See "Inheritance and Keymaps" in the elisp manual.

Answer 2

Note that olab b's answer is totally fine, and I gonna accept his answer and use it by myself, but there is one issue with it:

The original goto-map is untouched. Imagine hypothetical situation where some library provides a command which uses goto-map in some way - for example, to provide some fancy interface for commands in it or binds to it inside its own keymap - that is something that helm could do if they were crazy enough. Since all my keys would go to the my-goto-map, they wouldn't be reflected in those extensions.

So here is a weird solution for a made-up problem, just for funzies:

First, note that keymap in elisp can be one of two things:

  1. variable containing list whos first element is symbol 'keymap (and it is usually how keymaps are used in emacs)
  2. symbol whos function cell is set to the keymap variable (and I never saw that anybody used it like that)

So first we are going to define a variable for our keymap:

(defvar my-goto-map (make-sparse-keymap))

Next, write its value to the function cell as well:

(fset 'my-goto-map my-goto-map)

Note that we can't replace goto-map since everything that was already bound to it wouldn't catch the replacement. And manipulating with keybindinds one by one isn't fun. So here is a proper insert:

(setcdr goto-map (cons (make-composed-keymap 'my-goto-map (cons (car goto-map) (cdr goto-map))) nil))

(Of course we could just append my-goto-map to goto-map, but in that case the relation between my-goto-map and original goto-map would be "neighbors" and not "parent-child"; and "neighboring" does not allow for undefining keybindings)

Note that goto-map now depends on the symbol my-goto-map so we can do whatever we want with that symbol without loosing our keymap. So we can easily disable all custom keybindings like so:

(fset 'my-goto-map nil)

And the variable cell of my-goto-map is untouched, so we can easily restore keybindings:

(fset 'my-goto-map my-goto-map)

The main drawback comes from the fact that emacs does not allow "buffer-local functions", only "buffer-local variables". And here we need a local function :( So we can't just make that activation of prefix map in the hook of the minor mode - cause that modification would be reflected globally.

But at least all the original prefix keys for that keymap (which is one and only M-g IIRC) would automatically use all the custom keybindings, so no need to hardcode M-g as a prefix key in the my-goto-map.