Autocompletion keymap precedence again

I think there may be another bug in the autocompletion keymap precedence. I think the autocompletion keymap, which the code suggests should have highest precedence, is operating at default precedence, meaning that another keymap that handles up and down arrow keys and has default precedence will prevent arrow keys navigating the autocomplete options, which seems wrong and I’m pretty sure used not to be the case, although I haven’t proved that.

In the following example, the down arrow key (incorrectly, I think) has no effect when the autocompletion pop-up is visible:

Demo

Lowering the precedence of the custom keydown extension in the previous example fixes the problem:

Demo

It’ll have the highest precedence of any keymaps. Raw dom event handlers that were added with a higher precedence than any keymaps will override it.

That makes sense and I did wonder whether that was how it worked, but the interaction between keymaps and DOM keydown event handlers added via a view plugin is confusing to me. Also, I’m now certain that it has changed since version 6.0.0.

Here is a series of examples. In each case, there is a keymap extension and a DOM keydown handler extension, and in each case I’m recording which one “wins” (i.e. runs first).

[domKeydownExt, keymapExt]: DOM keydown wins
[keymapExt, domKeydownExt]: keymap wins

So far, as I’d expect.

[Prec.low(domKeydownExt), keymapExt]: keymap wins

OK…

[keymapExt, Prec.high(domKeydownExt)]: DOM keydown wins
[domKeydownExt, Prec.high(keymapExt)]: DOM keydown wins

This seems inconsistent, and this last case has definitely changed since the 6.0.0 release (example).

I’m really not sure what the behaviour should be but it would be good if it was documented, because there are definitely cases where you might want to use both keymaps and DOM keydown handlers. The @replit/codemirror-vim and @replit/codemirror-emacs packages both use a custom keydown handler, for example.

Do you have any thoughts on this, Marijn? From the examples above, if you have a single keymap and a single DOM event handler, it’s clearly not as simple as the one with the highest precedence wins. It’s difficult (for me, at least) to see any coherent logic.

One possibility that has occurred to me for the case of an extension that wants to handle all key events while also playing nicely with keymaps is implementing a catch-all keybinding or keymap. This would then work within the keymap world, which does have predictable precedence behaviour on its own.

The way it currently works is that the keymap handler gets default precedence, and thus takes effect after high-precedence DOM handlers, or DOM handler at default precedence added before it, and before low-precedence ones or default precedence ones added after it. Is that not what you are seeing?

Ahhh. That is exactly what I’m seeing. The crucial fact I didn’t understand is that wrapping a keymap in Prec.whatever() has no effect on its precedence relative to non-keymap extensions, and instead only affects its precedence relative to other keymaps. I do think a note in the docs mentioning this would be helpful, especially since the System Guide page uses keymaps to illustrate extension precedence.

This behaviour makes sense. However, the fact that a key handler will either run before all keymaps or after all keymaps is quite limiting. For some uses, you may want a key handler that is higher precedence than some keymaps and lower than others. An example is the Emacs keybindings for CM6 package: keybindings are necessarily implemented as a DOM key handler, which in the example set-up runs before all keymaps. When autocompleting in Emacs mode, the autocomplete arrow key bindings, which are wrapped in Prec.highest(), do not work because the key handler handles the event first.

My suggestion of a catch-all keybinding or keymap would solve this. What do you think?

That seems reasonable. This patch adds it (use keybindings like {any: (view, event) => ...}).

1 Like