Skip to content

`bind' many commands to keys in many keymaps, multiple times with support for prefix, autoload, repeat-mode and save&restore.

License

Notifications You must be signed in to change notification settings

repelliuss/bind

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Table of Contents

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.

(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) ; same

Can 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)

Upstreams

Available on 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.

  1. If BIND-FIRST is a keymap then BIND-FIRST
  2. If BIND-FIRST a function call then
    1. If BIND-FIRST is a call to 'bind-safe function (a symbol that has 'bind-safe prop), then first of it is output
    2. Otherwise first argument to function call (like to setq).
  3. 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,

(put :prefix 'lisp-indent-function 1)

See a processing function I use 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 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 commentary for what it brings.

FAQ

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.

About

`bind' many commands to keys in many keymaps, multiple times with support for prefix, autoload, repeat-mode and save&restore.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published