The problem I'm working to reach is allowing packages to correctly shadow the user's bindings (and not incorrectly shadow) instead of ad-hoc imitation of the global map defaults.  Ad-hoc imitation makes it very tedious to, for instance, change the key sequence for expressing "next" without losing coherence among all packages with a corresponding "next" concept.

The solution we're discussing is to provide "abstract" commands.  I will discuss the solution and then discuss what I'm currently working on.

Abstract commands are just command remap targets in the global map.  The concrete global commands would directly command remap in the global map.   Abstract command bindings would be consumed when modes generate keymaps for major & minor modes.  A Corfu map generator would for example see an abstract command like "user-next" bound with the C-n sequence and choose to shadow C-n in its map.  If the user rebinds the global abstract command, the other modes could easily follow this change, perhaps even though a hook to immediately propagate the change.

This scheme would scale much better than imitating global map defaults.  Any user or emulation system desiring to re-bind basic concepts like next and undo could achieve and maintain coherence.  Such a mechanism should also make it easier to maintain modes in the future.  Expressing abstract commands could be a basis for coordinating special mode behavior in ways that would currently be likely to break with many users' modifications.

I will not introduce a modal system I've worked on, but it has a quirky notion of next / previous, and yet it will adapt fine to abstract key bindings.  When switching keyboards to an alternative layout, I lose physical correspondence.  "J" is a good high-frequency binding unless "J" is moved to a physically inconvenient location.  Switching abstract maps and regenerating is a solution for this as well.

Expressing "never shadow this key" would need a distinct map from the global map.  The problem is that an abstract command like "user-nomap" would not distinctly map to a concrete command if 20 sequences were to be protected from shadowing.  There needs to be a global-no-map where all sequences bound to user-nomap are prohibited by convention.

For generating maps where a binding would collide with an abstract binding, a configurable successor function could be made available.  It could be set to nil or return nil to flatly reject all sequence suggestions that were bad to begin with instead of finding places to place every command.  We have really good command completions these days, so personally I would opt for a nil successor.

Abstract key definition would be adopted by first providing map generators for many modes (similar to Evil collection) and then expecting those map generators to migrate out to the modes themselves if the abstract map support were to be adopted into Emacs itself.

Regarding where I'm starting out:

I'm working on a more modest and immediate goal of depopulating unwanted bindings, focusing on overly complex or high-priority bindings first.  I will define my future abstract keys implementation in the same package.  While users in the future should not have to battle with so many bindings in the many mode maps, for now it will be appropriate to generate mass-unbind statements for packages like general to consume.

I'm using a report-and-configure workflow.  This is the lowest effort implementation to identify "bad" bindings and provide a fast remedy to the user.  While I can see many maps in for example minor-mode-map-alist, I was unsure of where to find a list of major modes except from inferring while using mapatoms.  I could notice the cost of calling this to find major modes / maps.

State tracking is necessary to differentiate user defined bindings from defaults.  While I had not wanted to add the complexity, inheriting keymaps may provide an elegant solution.  My hunch is to first destructively clean out default maps and then define inheriting maps and swap the inheriting maps into the keymap variables.  If the user adds bindings afterward, it's almost clearly visible.  Is there a way I could add a sentinel to an inherited map to indicate that I've cleaned the parent map already?  What are some good choices for list elements of no consequence?  This would also tell me that the child was created for the user.  I don't want to implement package-private state tracking if possible.

I have no ambition to work with non-list maps at this time.  So far every map I want to work with is just a plain list.

While working on my implementation, I have encountered the kinds of issue that make me believe that alternatives such as key-sequence translation are also not scalable.  (key-binding "\M-g") is incorrect after remapping M-g to C-g unless I collect a key sequence interactively.   Keeping this kind of indirection around has consequences.  I believe key sequence remapping is a solution more aimed at misbehaving input systems where correct bindings cannot accommodate the situation.

On Mon, Nov 21, 2022 at 8:07 PM Phil Sainty <psainty@orcon.net.nz> wrote:
AFAIK...

You can't arbitrarily prevent key sequences from being bound in
keymaps, because you can't control how or when that happens.

You can't control when it happens because that time might be "before
you started (or even installed) Emacs".  Keymaps are sometimes
generated at byte-compilation time, and thus even if you were to take
the extreme measure of preventing define-key from doing anything at
all, you will still acquire populated keymaps when certain libraries
are loaded (although if the libraries were byte-compiled while the
neutralised define-key was in place, you would then typically be
loading empty keymaps; but you would need to recompile everything to
get to that point; and of course most installations of Emacs will
include pre-compiled .elc files).

Native compilation might preclude even that, as IIRC it compiles
everything asynchronously in isolation, so the intended clobbering
of define-key might not actually be in effect when the native code
was being generated.  You would then have to edit the core code to
enforce your override irrespective of whether your custom code was
loaded.

You can't control how it happens because libraries can set bindings
without using define-key.  In the simplest case `use-local-map' for
one of the aforementioned pre-generated keymaps may easily occur; but
also a keymap is, after all, just a list, so it can be manipulated in
any number of ways.

(In practice I think that messing with define-key and recompiling All
Of The Things would affect almost all keymaps; but in general there's
no guarantee.)

I would suggest that your best option is to instead ensure that the
key lookup sequence always finds your preferred bindings.  Despite
your concerns about that approach, it seems more viable to me.



--