- Show, don’t tell
- Upstreams
- About =bind=
- Comparison with other key binders
- Package configurator support
- FAQ
Key bindings live in keymaps and keymaps are active depending on active major mode and minor modes.
You have define-key function in Emacs, which binds a command to a key sequence in a map.
(define-key my-window-map "d" #'delete-window)This is how you do it with bind.
(bind my-window-map "d" #'delete-window)First I have to define my-window-map.
(bind (setq my-window-map (make-sparse-keymap)) "d" #'delete-window)If you want to bind many commands to keys,
(bind my-window-map
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)If you want to bind those bindings in multiple maps,
(bind (my-window-map my-other-window-map)
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)I will be using those keymaps in multiple places.
(defun my-window-keymaps ()
(list my-window-map my-other-window-map))
(bind (my-window-keymaps)
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)I want to bind my-open-map I previously defined.
(bind my-window-map
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right
"o" my-open-map)Jeez, I forgot that I was going to do it in my-leader-map.
(bind (my-window-map
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right
"d" #'delete-window)
(my-leader-map
"o" my-open-map))I want to prefix each window command with w key.
(bind my-window-map
(bind-prefix "w"
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right))Never mind, I will just bind my-window-map to w in my-leader-map.
(bind (my-window-map
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
(my-leader-map
"o" my-open-map
"w" my-window-map))Shoot, something went wrong let me undo changes.
(bind-undo (my-window-map
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
(my-leader-map
"o" my-open-map
"w" my-window-map))I just want to unbind keys.
(bind-undo my-window-map "h" "j" "k" "l")Hey, I want to make use of repeat-mode, when enabled, for my window keys.
(bind my-window-map
(bind-repeat
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right))Let’s bind some keys for vertico package.
(bind vertico-map
"M-j" #'vertico-next
"M-k" #'vertico-previous
"M-J" #'vertico-next-group
"M-K" #'vertico-previous-group
"M->" #'vertico-scroll-up
"M-<" #'vertico-scroll-down
"C->" #'vertico-last
"C-<" #'vertico-first)Hmm, can I prefix those with a modifier key?
(bind vertico-map
(bind-prefix "M-"
"j" #'vertico-next
"k" #'vertico-previous
"J" #'vertico-next-group
"K" #'vertico-previous-group
">" #'vertico-scroll-up
"<" #'vertico-scroll-down)
(bind-prefix "C-"
">" #'vertico-last
"<" #'vertico-first))Good! Let’s autoload vertico when a command is called in my-leader-map that is not yet loaded (and not autoloaded by package).
(bind my-leader-map
(bind-autoload :vertico
"r" #'vertico-repeat))I’ve gone mad. I want to put window movement commands under key m and layout commands under l.
(bind my-window-map
(bind-prefix "m"
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
"d" #'delete-window
"D" #'delete-other-windows
(bind-prefix "l"
"b" #'split-window-below
"r" #'split-window-right))Hmm, it would be good if I could also repeat them and just autoload layout commands.
(bind my-window-map
(bind-repeat
(bind-prefix "m"
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
"d" #'delete-window
"D" #'delete-other-windows
(bind-autoload :my-package
(bind-prefix "l"
"b" #'split-window-below
"r" #'split-window-right))))Let’s bind my-leader-map to global map at the end.
(bind (my-window-map
(bind-repeat
(bind-prefix "m"
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
"d" #'delete-window
"D" #'delete-other-windows
(bind-autoload :my-package
(bind-prefix "l"
"b" #'split-window-below
"r" #'split-window-right))))
(my-leader-map
"o" my-open-map
"w" my-window-map)
((current-global-map)
"SPC" my-leader-map))This is all good. I get that they are just functions evaluating arbitrary code that take bindings and return bindings in the end. But they are a bit verbose and I don’t usually evaluate them myself for debugging. Is there a concise way?
(bind (my-window-map
(:repeat
(:prefix "m"
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
"d" #'delete-window
"D" #'delete-other-windows
(:autoload :my-package
(:prefix "l"
"b" #'split-window-below
"r" #'split-window-right))))
(my-leader-map
"o" my-open-map
"w" my-window-map)
((current-global-map)
"SPC" my-leader-map))Can I provide a help string like in define-key?
(bind my-window-map
"d" (cons "Delete current window" #'delete-window))Great! So it is define-key like at the end. Now I want to bind a command in c-mode locally.
(add-hook 'c-mode-hook
(lambda ()
(bind (bind-local-map)
"k" #'my-command)))Hmm, (bind-local-map) is a function and seems to be returning a keymap just like how local-set-key does. Is there a global counterpart, just to complement each other?
(bind (bind-global-map) "SPC" my-leader-map)
(bind (:global-map) "SPC" my-leader-map) ; sameCan I still remap a command just like in define-key?
(bind help-map [remap define-function] #'my-define-function)All is good, but sometimes I do mistakes and lose my previous bindings. Is there a way I can safely bind keys?
;; create a save of current definitions
(setq save
(bind-save (bind-global-map)
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right))
;; bind new definitions
(bind (bind-global-map)
"h" #'windmove-left
"j" #'windmove-down
"k" #'windmove-up
"l" #'windmove-right)
;; restore old definitions
(bind-restore save)Available on Melpa.
Syntax is (bind FORM) or (bind (FORM)...) so (FORM) is
repeatable.
FORM’s first element can be a keymap, list of keymaps, a function
returning keymap (setq) or keymaps (a user function). It is
quoted, if it is a keymap or a list of keymaps.
FORM’s rest elements must be bindings. A binding is in the form
of KEY DEF where KEY and DEF has the same specs as in
define-key, in the case of bind.
Here are some gists about bind.
- Every key binding in Emacs lives in a key map. Instead of providing different functions for specific cases,
bindsuggests one function. - Putting multiple
bindforms in onebindcall is same as calling each one on its own. - There are processing functions like
bind-prefix,bind-autoloadetc. which takes bindings and acts on them and returns bindings, possibly modified. Those can be nested as however wanted.bindcarries information, metadata, at upper levels to lower levels and then processing function propagates backwards. - Processing functions are just any other arbitrary functions you can evaluate arbitrary code.
All atoms in bind form can instead typed without package prefix (without bind- or bind--) in keyword form. For example, bind-prefix can be written as :prefix and bind-global-map as :global-map etc.
You lose the ability of evaluating them without expanding the macro but this is not needed most of the time except debugging.
Simplest processing function, prefixes each key with given prefix. Understands modifier prefixes.
Support for repeat-mode. Puts repeat-map property to definitions in bindings for bind :main property in metadata, unless a target map is given. Note that definitions also need to be in that target map for repeat-mode to work.
Make sure repeat-mode is enabled.
Autoload definitions in bindings. If first argument to function is a symbol, then autoload that feature. Otherwise try to retrieve :main-file prop from metadata.
bind doesn’t provide that prop but package configurators usually have that info which they can provide it in their bind support.
Return a save of current definitions instead of binding them in bind FORM. This way, you can safely use bind and restore in case the result is unwanted.
Restores a save returned from bind-save.
unbind sounds nice with bind instead of bind-undo. It is not called that way because package conventions but no one is limiting you.
At the end of everything, bind--definer is called with KEYMAP, KEY and DEF (arguments to define-key). You can lexically change that variable and call bind in your own function for custom behaviors.
bind--metadata is a lexical plist that carries information populated by upper bind calls to use from lower bind calls (nesting wise) so that information isn’t repeated.
bind provides following properties.
Calling top level bind macro when bind is not used.
For example, its value will be nil if bind is used and 'bind-undo if bind-undo is used.
Processor functions take different actions depending on different engines. For example bind-repeat my remove repeat property from definitions instead of putting them when bind-undo is used.
Contains the main keymap for current bind forms.
Following is the logic for resolving bind main, in order,
BIND-FIRST is the first element of bind FORM.
- If
BIND-FIRSTis a keymap thenBIND-FIRST - If
BIND-FIRSTa function call then- If
BIND-FIRSTis a call to'bind-safefunction (a symbol that has'bind-safeprop), then first of it is output - Otherwise first argument to function call (like to
setq).
- If
- Otherwise first element of
BIND-FIRST.
Only put ‘bind-safe to a function if function doesn’t mutate data.
See bind-autoload for a use case.
All a processor function must do is taking bindings and returning them, possibly modified. While doing so it can provide other utilities through bindings.
Users are encouraged to define their own function which they can then evaluate arbitrary code.
User is encouraged to make use of bind-keyp, bind-foreach-key-def, bind-flatten1-key-of-bindings and bind-with-metadata utility functions for their custom behavior. See default processing functions’ definitions for examples.
bind-flatten1-key-of-bindings is especially useful because processing functions shouldn’t assume bindings will be in (KEY DEF)+ but ((KEY DEF)|((KEY DEF)+))+ form due to inner processing functions returning bindings in a list.
You should also put indentation to its synonym for proper indentation. For example, bind-prefix defines,
(put :prefix 'lisp-indent-function 1)See a processing function I use here.
In the extensions/ directory, you can grab your particular configuration. These won’t be installed by default so you have to manually install them or through your package manager if it has a support for this.
See (bind-setup-integrate keyword) which will add KEYWORD to setup. Please read commentary for what it brings.
See (bind-use-package-integrate keyword &optional anchor after test) which will add KEYWORD to use-package. Please read commentary for what it brings.
I’ve used evil-mode before but I am not sure of its requirements or why it needs a special treatment. I suppose a smart function/macro returning maps based on synonyms should be enough. I am open to talk about it.