I would like to have a function call-keymap which takes one argument km (a keymap) and such that the result of (call-keymap km) is the same as binding km to a key and then pressing that key.

It should look something like this

(defun call-keymap (km)
    (while (keymapp km)
      (setq km (lookup-key km (read-key-sequence ""))))
   (call-interactively km))

except that this doesn't work (specifically the last line is wrong, since a non-keymap entry of a keymap needn't be a function).

Intuitively, it would seem that the main command loop in emacs should be something like

(while t
  (call-keymap (current-global-map))

(or rather, it should do something with current-active-maps instead of current-global-map).

I was surprised to not be able to find a built-in function with the desired functionality of call-keymap, since this seems to me to be such a basic function.

Could someone please point me to such a built-in, or indicate how to fix the above definition?

Edit 2019-01-23

Stefan very rightly asked that I explain what my use-case is for such a function.

I use evil-mode and have SPC as a prefix-key for various useful commands when in normal-state. In insert-state, I would like some other binding (say, M-SPC) to produce the same behaviour as SPC in normal mode (I think emacs-DOOM has such a feature). My idea was then to bind M-SPC to a function which fetches the current normal-state binding of SPC (which is a keymap) and calls it.

Presumably, there are other good solutions to this, so that my original question may be moot, though I would be still be interested to know the answer. For example, here is one quite-hacky-but-maybe-not-entirely-unsatisfactory solution to my problem:

(defun set-alt-space ()
    (evil-local-set-key 'insert (kbd "M-SPC") (key-binding (kbd "SPC"))))
(add-hook 'evil-normal-state-exit-hook 'set-alt-space)
  • 2
    Can you give us some example use-case for such a function? – Stefan Jan 23 '20 at 13:34
  • I think spacemacs does something like what you ask for in the edit. Maybe it is done in a simple way you can copy? The naive solution is to bind both normal mode SPC and insert mode M-SPC to the same key map but this doesn’t work with mode maps in the mix. – Dan Robertson Jan 24 '20 at 22:46

Assuming your keymap is a menu, you can use popup-menu.

If it's not a menu but is just a normal keymap for normal keys (e.g. typically bound to a prefix key), then I don't think there's a predefined function for that. But in your example call-keymap function, if you replace call-interactively with execute-command, should cover the majority of cases. Of course, it still won't reproduce the normal way commands are executed because it fails to set this-command, last-command, run pre/post-command-hook, handle the prefix-arg, move cursor out of invisible text, etc...

To faithfully reproduce the way the command loop runs commands, the only good way is to ... use the command loop. One way is with recursive-edit, but that covers a sequence of commands rather than just one (and it needs to be exited "manually").

In your case, I think the better answer is set-transient-map. E.g. your M-SPC command could look something like:

(defun my-one-time-normal-SPC ()
  (let ((map (lookup-key evil-normal-state-map "\s")))
    (set-transient-map map)))
  • Yes, I had tried that as well (or rather, with command-execute), but it still fails, for example, when self-insert-command is in the keymap -- and in any case, I am still left with the feeling that there should be a very canonical way to do this... – Joj Helfer Jan 24 '20 at 1:25
  • @JojHelfer: OK, I added info to my answer. – Stefan Jan 24 '20 at 13:20
  • Thank you! set-transient-map is just the thing! And it is indeed mentioned in the elisp docs; I had just missed it. – Joj Helfer Jan 24 '20 at 18:35

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.