bind
bind copied to clipboard
`bind' many commands to keys in many keymaps, multiple times with support for prefix, autoload, repeat-mode and save&restore.
- Table of Contents :TOC:
- [[#show-dont-tell][Show, don't tell]]
- [[#upstreams][Upstreams]]
- [[#about-bind][About =bind=]]
- [[#bind-prefix][=bind-prefix=]]
- [[#bind-repeat][=bind-repeat=]]
- [[#bind-autoload][=bind-autoload=]]
- [[#bind-save][=bind-save=]]
- [[#bind-restore][=bind-restore=]]
- [[#alias-bind-undo-to-unbind][Alias =bind-undo= to =unbind=]]
- [[#enhancing-bind][Enhancing =bind=]]
- [[#comparison-with-other-key-binders][Comparison with other key binders]]
- [[#package-configurator-support][Package configurator support]]
- [[#setupel][setup.el]]
- [[#use-package][use-package]]
- [[#faq][FAQ]]
- [[#evil][evil]]
- Show, don't tell
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.
#+begin_src elisp (define-key my-window-map "d" #'delete-window) #+end_src
This is how you do it with =bind=.
#+begin_src elisp (bind my-window-map "d" #'delete-window) #+end_src
First I have to define =my-window-map=.
#+begin_src elisp (bind (setq my-window-map (make-sparse-keymap)) "d" #'delete-window) #+end_src
If you want to bind many commands to keys,
#+begin_src elisp (bind my-window-map "h" #'windmove-left "j" #'windmove-down "k" #'windmove-up "l" #'windmove-right) #+end_src
If you want to bind those bindings in multiple maps,
#+begin_src elisp (bind (my-window-map my-other-window-map) "h" #'windmove-left "j" #'windmove-down "k" #'windmove-up "l" #'windmove-right) #+end_src
I will be using those keymaps in multiple places.
#+begin_src elisp (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) #+end_src
I want to bind =my-open-map= I previously defined.
#+begin_src elisp (bind my-window-map "h" #'windmove-left "j" #'windmove-down "k" #'windmove-up "l" #'windmove-right "o" my-open-map) #+end_src
Jeez, I forgot that I was going to do it in =my-leader-map=.
#+begin_src elisp (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)) #+end_src
I want to prefix each window command with ~w~ key.
#+begin_src elisp (bind my-window-map (bind-prefix "w" "h" #'windmove-left "j" #'windmove-down "k" #'windmove-up "l" #'windmove-right)) #+end_src
Never mind, I will just bind =my-window-map= to ~w~ in =my-leader-map=.
#+begin_src elisp (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)) #+end_src
Shoot, something went wrong let me undo changes.
#+begin_src elisp (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)) #+end_src
I just want to unbind keys.
#+begin_src elisp (bind-undo my-window-map "h" "j" "k" "l") #+end_src
Hey, I want to make use of =repeat-mode=, when enabled, for my window keys.
#+begin_src elisp (bind my-window-map (bind-repeat "h" #'windmove-left "j" #'windmove-down "k" #'windmove-up "l" #'windmove-right)) #+end_src
Let's bind some keys for [[https://github.com/minad/vertico][vertico]] package.
#+begin_src elisp (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) #+end_src
Hmm, can I prefix those with a modifier key?
#+begin_src elisp (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)) #+end_src
Good! Let's autoload [[https://github.com/minad/vertico][vertico]] when a command is called in =my-leader-map= that is not yet loaded (and not autoloaded by package).
#+begin_src elisp (bind my-leader-map (bind-autoload :vertico "r" #'vertico-repeat)) #+end_src
I've gone mad. I want to put window movement commands under key ~m~ and layout commands under ~l~.
#+begin_src elisp (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)) #+end_src
Hmm, it would be good if I could also repeat them and just autoload layout commands.
#+begin_src elisp (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)))) #+end_src
Let's bind =my-leader-map= to global map at the end.
#+begin_src elisp (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)) #+end_src
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?
#+begin_src elisp (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)) #+end_src
Can I provide a help string like in =define-key=?
#+begin_src elisp (bind my-window-map "d" (cons "Delete current window" #'delete-window)) #+end_src
Great! So it is =define-key= like at the end. Now I want to bind a command in =c-mode= locally.
#+begin_src elisp (add-hook 'c-mode-hook (lambda () (bind (bind-local-map) "k" #'my-command))) #+end_src
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?
#+begin_src elisp (bind (bind-global-map) "SPC" my-leader-map) (bind (:global-map) "SPC" my-leader-map) ; same #+end_src
Can I still remap a command just like in =define-key=?
#+begin_src elisp (bind help-map [remap define-function] #'my-define-function) #+end_src
All is good, but sometimes I do mistakes and lose my previous bindings. Is there a way I can safely =bind= keys?
#+begin_src elisp ;; 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) #+end_src
- Upstreams
Available on [[https://www.melpa.org/#/][Melpa]].
- About =bind=
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, =bind= suggests one function.
- Putting multiple =bind= forms in one =bind= call is same as calling each one on its own.
- There are processing functions like =bind-prefix=, =bind-autoload= etc. which takes bindings and acts on them and returns bindings, possibly modified. Those can be nested as however wanted. =bind= carries 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.
** Synonyms
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.
** =bind-prefix=
Simplest processing function, prefixes each key with given prefix. Understands modifier prefixes.
** =bind-repeat=
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.
** =bind-autoload=
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.
** =bind-save=
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.
** =bind-restore=
Restores a save returned from =bind-save=.
** Alias =bind-undo= to =unbind=
=unbind= sounds nice with =bind= instead of =bind-undo=. It is not called that way because package conventions but no one is limiting you.
** Enhancing =bind= *** =bind--definer=
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.
*** Metadata
=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.
**** =:engine=
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.
**** =:main=
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-FIRST= is a keymap then =BIND-FIRST=
- If =BIND-FIRST= a function call then
- If =BIND-FIRST= is a call to ='bind-safe= function (a symbol that has ='bind-safe= prop), then first of it is output
- Otherwise first argument to function call (like to =setq=).
- 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.
*** Processor functions
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,
#+begin_src elisp (put :prefix 'lisp-indent-function 1) #+end_src
See a processing function I use [[https://github.com/repelliuss/bind/wiki#prefix-keys-with-user-mode-local-key-sequence][here]].
- Package configurator support
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.
** setup.el
See =(bind-setup-integrate keyword)= which will add KEYWORD to =setup=. Please read [[https://github.com/repelliuss/bind/blob/3a9bcb8d7e42aca57a982565f05a80b2682e7370/extensions/bind-setup.el#L30][commentary]] for what it brings.
** use-package
See =(bind-use-package-integrate keyword &optional anchor after test)= which will add KEYWORD to =use-package=. Please read [[https://github.com/repelliuss/bind/blob/3a9bcb8d7e42aca57a982565f05a80b2682e7370/extensions/bind-use-package.el#L30][commentary]] for what it brings.
- FAQ ** [[https://github.com/emacs-evil/evil][evil]] 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.