Is there a way to prompt the user for a key sequence, like read-key-sequence, but only including keys from a specified keymap? It seems like read-key-sequence always reads from all active keymaps.
- 41
- 1
1 Answers
Best I've been able to figure out:
(defun read-key-sequence-in-keymap (keymap &rest arguments)
(let ((overriding-terminal-local-map nil)
(overriding-local-map keymap)
(saved-global-map (current-global-map)))
(unwind-protect
(progn
(use-global-map (make-sparse-keymap))
(apply 'read-key-sequence arguments))
(use-global-map saved-global-map))))
Usage Notes
Since read-key-sequence doesn't distinguish if the key sequence reaches a non-prefix bound key or an unbound key, combine it with something like lookup-key if you need to tell those cases apart (note the t argument at the end of lookup-key makes it respect default bindings the same way that read-key-sequence does):
(let ((key-sequence (read-key-sequence-in-keymap my-keymap ...)))
(lookup-key my-keymap key-sequence t))
Explanation
The big picture: activate the keymap we're interested in, and disable all other keymaps. This limits read-key-sequence to just our keymap.
The details:
Keymaps can come from many different variables, and even from text under point, but overriding-local-map is uniquely special: it does not merely take precedence, it causes most other keymaps to not even get looked at.
The easiest way to see this is in the pseudo code in the Emacs documentation for how keymaps are searched (notice how most of the search is in the "else" branch of if overriding-local-map):
(or (if overriding-terminal-local-map
(find-in overriding-terminal-local-map))
(if overriding-local-map
(find-in overriding-local-map)
(or (find-in (get-char-property (point) 'keymap))
(find-in-any emulation-mode-map-alists)
(find-in-any minor-mode-overriding-map-alist)
(find-in-any minor-mode-map-alist)
(if (get-char-property (point) 'local-map)
(find-in (get-char-property (point) 'local-map))
(find-in (current-local-map)))))
(find-in (current-global-map)))
So simply assigning the keymap we're interested in to overriding-local-map is most of the solution, and we just have to clear overriding-terminal-local-map and the global map. The first is trivial - just set it to nil. But the second requires a little extra fiddling:
- we have to use
use-global-mapto set it since it doesn't live in a global variable (there'sglobal-mapbut that's just a reference to the default global map, not the current one); - Emacs won't let us set it to
nil, so we have to create a new empty keymap (by the way, an empty sparse keymap is just'(keymap)); and - we have to make sure we restore the global keymap when we're done.
Questions
Does this break input methods?
My own design intuition based on what I know would be to implement the entry into key-translating functions using something like key-translation-map or a "special event" (aiui: binding that execute as soon as they're read, before the normal key sequence read and lookup is done), and that should be fully compatible with this.
(The Quail library that ships with Emacs uses overriding-terminal-local-map in its implemention of such input methods, but glancing at the source, it seems to only do this temporarily in their own let form, during their own code, with little to no opportunity for other code to run, so that part seems compatible with this.)
Bugs
When read-key-sequence-in-keymap is ran inside a (with-temp-buffer ...), and input comes from a terminal (not GUI) Emacs frame, hitting the Escape key causes the read to wait for more input until I hit any other key.
- 346
- 2
- 13
read-key-sequencedoesn't pay attention to any keymaps. It reads a key sequence that Emacs can recognize - regardless of whether it is bound in any keymap. A key sequence need not be bound to any command. – Drew Aug 27 '21 at 06:25map-keymapto get key descriptions of all keys in a keymap, and then use those descriptions as completion candidates withcompleting-read. – Drew Aug 27 '21 at 06:27read-key-sequencedoes pay attention to the currently active keymaps - if you pressC-xafter runningread-key-sequence, it will wait for you to press more keys becauseC-xis globally bound to a keymap, but if you press an unmapped key or key bound to a command, it will return the sequence. What I am looking for is a command to do the same thing but only taking into account a specific keymap. – Prismavoid Aug 28 '21 at 04:25C-x. You can typeC-x 7, even though that key sequence is unbound. – Drew Aug 28 '21 at 16:55C-x, enough to know thatC-x 5is yet another prefix and read one more key after that. Literally one of the first lines in the built-in help: "The sequence is sufficient to specify a non-prefix command in the current local and global maps." It stops atC-x 7because7is not bound in theC-xmap, so it has ruled out the prefix case. It doesn't stop atC-x 5because it looked into theC-xkeymap and seen that5is bound to yet another keymap, and it will look in there too to see if the next key is also a prefix, and so on. – mtraceur Sep 10 '23 at 16:11