While working with read-key-sequence-vector
and read-key
, I found that the former applies the local-function-key-map
translations in a conditional manner but the latter does not. I am using Emacs 28.1 on a macOS system.
The function read-key-sequence-vector
translates the original key sequence to a key sequence in the local-function-key-map
only if the original key sequence is unbound. If the original key sequence is bound, then the translations in local-function-key-map
are not applied.
However, according to my observations, the function read-key
always seems to apply the local-function-key-map
translations in an unconditional manner. I would like to understand if this is a bug or expected behavior. The sections below explain the investigation I have done so far.
Documentation: read-key-sequence-vector
The documentation for read-key-sequence-vector
at § 22.8.1 Key Sequence Input refers to read-key-sequence
which says:
Function: read-key-sequence prompt &optional continue-echo dont-downcase-last switch-frame-ok command-loop
...
Reading a key sequence includes translating the events in various ways. See Keymaps for Translating Sequences of Events.
The documentation for § 23.14 Keymaps for Translating Sequences of Events says:
Variable: local-function-key-map
This variable holds a keymap similar to
input-decode-map
except that it describes key sequences which should be translated to alternative interpretations that are usually preferred. It applies afterinput-decode-map
and beforekey-translation-map
.Entries in
local-function-key-map
are ignored if they conflict with bindings made in the minor mode, local, or global keymaps. I.e., the remapping only applies if the original key sequence would otherwise not have any binding.
Now open a fresh new Emacs:
emacs -q
Then type:
C-h v local-function-key-map RET
The output contains this entry:
(tab . [9])
Confirm the above observation by entering the following:
M-: (lookup-key local-function-key-map [tab]) RET
It should display the following output:
[9]
We have seen so far that if the <tab>
key is unbound, then a key sequence [tab]
should get translated to [9]
. We'll see an example of this in an experiment presented later.
Documentation: read-key
The documentation for read-key
at § 22.8.2 Reading One Event says:
Function: read-key &optional prompt disable-fallbacks
This function reads a single key. It is intermediate between
read-key-sequence
andread-event
. Unlike the former, it reads a single key, not a key sequence. Unlike the latter, it does not return a raw event, but decodes and translates the user input according toinput-decode-map
,local-function-key-map
, andkey-translation-map
(see Keymaps for Translating Sequences of Events).
It seems that the translation rules that apply for read-key
are no different from that of read-key-sequence-vector
. The same documentation further says:
If argument disable-fallbacks is non-
nil
then the usual fallback logic for unbound keys inread-key-sequence
is not applied. This means that mouse button-down and multi-click events will not be discarded andlocal-function-key-map
andkey-translation-map
will not get applied. Ifnil
or unspecified, the only fallback disabled is downcasing of the last event.
The disable-fallbacks
argument if set to t
unconditionally skips translations, so this argument does not seem relevant to my question here. My question here is about read-key
unconditionally applying translations, not about unconditionally skipping translations.
Experiment 1: Default Behaviour
Switch to a fundamental mode buffer:
C-x b foo RET
Now type the following:
C-h k <tab>
The following output should appear:
TAB (translated from <tab>) runs the command indent-for-tab-command(found in global-map), which is an interactive compiled Lisp functionin ‘indent.el’.
It shows that <tab>
(represented as the symbol tab
in the key sequence vector as we'll see later) is unbound and it is being translated to TAB
(also something we'll see soon).
Copy this Elisp code into the buffer:
(defun check-keys () (interactive) (let ((event (read-event "Event: ")) (key (read-key "Key: ")) (key-sequence-vector (read-key-sequence-vector "Key Sequence: "))) (message "event: %S (%S); key: %S (%S); key-sequence-vector: %S (%S)" event (type-of event) key (type-of key) key-sequence-vector (type-of key-sequence-vector))))
Now define this command by placing the cursor after the last parentheses and typing the following:
C-x C-e
Now execute the above command by typing:
M-x check-keys RET
This command prompts three times. Type tab all three times. The following output appears:
event: tab (symbol); key: 9 (integer); key-sequence-vector: [9] (vector)
This is expected behaviour. The function read-event
reads the next event (a tab key) as the symbol tab
. Since there is nothing bound to the key sequence [tab]
(a vector containing the symbol tab
), both read-key
and read-key-sequence-vector
translate it according to local-function-key-map
thereby obtaining 9
.
Experiment 2: Key Sequence [tab] Bound
Now define a key-binding for a key sequence containing the symbol tab
as follows:
M-: (global-set-key [tab] 'whitespace-mode) RET
Now verify the key binding with the following key sequence:
C-h k <tab>
The following output appears confirming that the <tab>
key itself is bound to the whitespace-command
command:
<tab> runs the command whitespace-mode (found in global-map), which isan autoloaded interactive Lisp function in ‘whitespace.el’.
Now run check-keys
again:
M-x check-keys RET
Once again, type tab at all three prompts. The following output appears:
event: tab (symbol); key: 9 (integer); key-sequence-vector: [tab] (vector)
Now the key sequence made of the symbol tab
is bound to the whitespace-mode
command. Therefore according to § 23.14 Keymaps for Translating Sequences of Events, the symbol tab
should not be translated to the integer 9
anymore. In fact, the above output shows that read-key-sequence-vector
indeed does not translate the symbol tab
to the integer 9
anymore. This shows that the conditional translation is working as documented in read-key-sequence-vector
.
However, the above output also shows that read-key
still translates the symbol tab
to the integer 9
. The behaviour of read-key
here seems to contradict § 22.8.2 Reading One Event which seems to imply that the conditional translation rules for local-function-key-map
should apply here too, i.e., no translation for tab
should occur when it is already bound to some command.
Does the behaviour of read-key
indeed contradict the documentation? Or is this expected behaviour? If this is expected behaviour, where in the documentation can we find why this is expected behaviour?