-
Notifications
You must be signed in to change notification settings - Fork 45
Description
I wrote a PoC a while ago with some minimal functionality (keymap/state aliases, :prefix handling, prefix map creation, auto kbd
, etc.). Once I've written tests and cleaned it up, I will create a branch for it. There is no timeline for this to become stable and replace general.el. This is not a priority for me since general.el works, and I don't think there's a huge interest in this, so it might be many years before that happens.
Summary of changes/tl;dr
- cleaner internal implementation and extension system
- more powerful extension system (internals implemented pretty much entirely through extension system)
- performance gotchas addressed
- better documentation
- leto.el - macro to expand to definer statements to minimal, human-readable code (for transparency to show what a definer actually does)
- more alternate definition syntaxes
- single definer recommended for most situations that doesn't have the caveats of
general-def
; short name (just the new package name) - more non-keybinding configuration helpers (in separate file)
Why rewrite?
- Simpler implementation. General was not so general in the beginning and has special handling for a lot of functionality (especially evil support being hardcoded as opposed to implemented using the extension system). The general.el internals should be generic too.
- Almost all functionality in the rewrite will be implemented as a linear sequence of transformations over keyword arguments and then keybindings.
- The extension system will be the same as the internal implementation. The only difference will be that helpers will be provided to insert keyword argument and keybinding parsers in the correct order in a forwards-compatible way (since parsers can transform/add new keybindings or generate metadata, order will matter)
- Transformations can alter keyword arguments (e.g. to expand keymap aliases) and keybindings (e.g. to apply
kbd
to the key). They can add new keybindings. They can generate metadata to be used by later parsers. They can return effects to happen before or after the keybindings. Effects will no longer be run directly in extension functions but instead returned as lambdas/functions or as quoted list (to support leto.el; see below). Functions are used instead in order to support running effects before or after the keybindings are created. - The extension system will be implemented in a forwards-compatible to allow later improvements without breaking existing parsers. In the PoC, the extension parser functions return plists (e.g. keybind parser:
(list :keybinds ... :effect ... :after-effect ...)
). I'm undecided on whether to pass parser functions keyword arguments or to just recommend &rest _ (since they take few args currently, will likely not take many more, and the keyword arguments are already passed as a plist). Maybe I will use a single plist argument, so that the return value of one parser can be passed directly to the next. - Drop support for older Emacs versions and use newer utility functions like
when-let
and especiallypcase-let
(with plist destructuring)
- Performance issues (Optimize general-define-key #180 and general-define-key macro implementation; no need to require general after compile #184) - these will probably matter less and less over time, but there are a couple of fundamental gotchas with general currently that can slow init time
- Generating prefixes when :global-prefix, :non-normal-prefix, and :prefix are specified is very slow. The current recommendation is to use prefix maps instead. The rewrite could potentially disallow using the :prefix keywords or give a warning (that can be disabled) in situations where it can cause slowdown.
- General by default will delay keybindings if a keymap doesn't exist (by adding a function to
after-load-functions
to check if the keymap now exists when loading new elisp). This is very slow. For example, if you create a bunch of evil keybindings before loading evil, loading evil will be very slow. Maybe this can be kept as opt-in to make migration easier, but usingwith-eval-after-load
, use-package's:config
, or maybe a newly added:general-config
should be recommended instead. - This won't be recommended for many reasons (e.g. various caveats like variables needing to be made available at expansion time since macros are not supposed to rely on the values of arguments and also the fact that it probably won't significantly improve startup time), but it will be technically possible to not even depend on any code from the rewrite at load-time by using leto.el and compiling the init file (general-define-key macro implementation; no need to require general after compile #184). Not sure how robust this can be (so far falling back to the normal function definer on void-variable errors seems to work fine), but it will at least be fun to look into further.
- At this point there are a lot of improvements I want to make that require other breaking changes. The syntax will mostly remain the same (or at least the previous syntax will remain an option).
Other improvements:
- Documentation rewrite
- Better introduction/tutorial at beginning
- Suggested best practices
- Full reference last
- Leto.el
- The vast majority of issues on the general.el repo are questions. General.el causes a lot of confusion for people not yet familiar with Emacs' keybinding system. In addition to having better introductory documentation, it would help if general.el was more transparent. An "instructor" should be provided to expand a definer to human-readable code.
- This would help users understand how the keybinding system works/what general.el is actually doing
- This would also help users understand whether an issue they are having is related to general.el (often it isn't)
- The new extension system can support this with almost no extra code for leto.el (parsers just need to have slightly different behavior depending on whether called from leto or not, e.g. return a quoted form instead of a lambda for an effect)
- Split into multiple files (load only what you use)
- Base file will only have the basic key definer
- Separate file for extra definer macros built on top of the basic key definer
- Separate file for keybinding helpers
- Separate file for leto.el
- Separate file(s) for non-keybinding helpers
- Separate file for some backwards-compatibility helpers to make migration easier
- More non-keybinding configuration helpers - General has its own hook, advice, etc. helpers already with syntactic compatibility with
add-hook
andadvice-add
and extra functionality (e.g. ability to add multiple functions to multiple hooks and the ability to conditionally remove the function from the hook). I have a long list of ideas for other configuration helpers (either from my own config or yet unwritten) that I think would be better as part of a library. There are also a lot of useful helpers in Doom Emacs, for example, that would be better as part of an external library. I haven't decided if they should be in a separate repo, but I will probably just keep them in a separate file. - Other long unimplemented todo items like modifier prefixes (
:prefix "C-"
) and annalist.el integration - Like keymap delaying, rethink other things. Make anything that requires guessing (e.g. :general has to guess if the first argument is a definer, general-def has to guess if arguments are keymaps or states, use-package words have to do some weird things for autoloading)
- Keymaps should probably still be quoted (even though not necessary for delaying, other uses like recording the keymap name to display later with annalist) (like
set
- can still buildsetq
on top of) - Rethink extended definition syntax. If Emacs adds support for a plist definition in the future, the current syntax could be problematic. Even if it doesn't, the current syntax is not ideal. Something like
key def :with (extended-def-plist...)
(or the equivalent with the plist preceding the definition) could be specially handled instead. I don't really love this non-standard use of keyword argument though and need to think about things more. Maybe this can just be one option and the alternate list for each keybinding syntax below can be recommended. - Also consider the best syntax for binding multiple keys at at once to a single command (this can be potentially useful in some cases)
- Consider alternate syntaxes:
(<key> <command> :extended-def-keyword <arg> ...)
would be much simpler to parse since the key and command are always in the same format. This will probably be the format for the internal base definer. A let-like (as opposed to setq-like) syntax will probably be optionally provided in a user-facing definer. Not sure what should be the default. - Rethink
general-def
equivalent. What should be the single recommended definer?general-def
tried to be close to a drop-in replacement for several other definers, but this means it has some caveats (e.g. the need to use :start-maps or some other bogus keyword here:(general-def keymap :start-maps t key-variable #'command)
). Probably it would be better have a definer that supports a variable number of positional arguments but does not try to be a drop-in replacement for other definers in order to avoid these caveats. - :prefix, :non-normal-prefix, and :global-prefix interaction is confusing. :prefix should always apply, and there should be :normal-prefix and :non-normal-prefix instead (old behavior can potentially be opt-in).
- Rethink how
:general
is handled (the check to determine whether the the first form is a definer or argument is not fully robust). - Remove autoload generation functionality? The only case in which this is useful is for functions you have defined yourself (if a package has not correctly defined autoloads, you can still manually use
autoload
and report it as a bug). For user-defined functions loaded after loading a package, I could just add an :autoload extend and remove all this code. - Drop :which-key keyword entirely?
which-key-replacement-alist
is slow and imperfect but it does provide functionality that the other method does not have. Maybe just recommend the user to manually add these more complex replacements (like those that use a function to determine the string to display).
- Keymaps should probably still be quoted (even though not necessary for delaying, other uses like recording the keymap name to display later with annalist) (like
The rewrite will not be fully compatible with general.el (things deprecated in the readme will finally be deprecated, the extension system will be different, delaying keybindings until the keymap exists will no longer be the default, etc.), so I will probably create a new package to avoid breaking people's configs. I'd like to keep the current name, but that doesn't seem practical. I guess I could create a separate repo and not put it on MELPA, but that would probably end up causing a lot more confusion.
Possible names:
- tyrant
- mogul
- baron
- consul (previously chosen name, but it would be too confusing now with both consult and counsel)