Skip to content

An emacs lisp framework to help organize emacs-lisp commands and code templates into a hierarchy of keymaps, and to facilitate installing and visualizing them

Notifications You must be signed in to change notification settings

erjoalgo/emacs-buttons

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EMACS-BUTTONS

https://travis-ci.org/erjoalgo/emacs-buttons.svg

“The power of emacs lisp at your fingertips”

doc/img/emacs-buttons.png

Description

emacs-buttons is an emacs lisp mini-language to help organize emacs-lisp commands and frequently-occurring code templates into a tree-like hierarchy of keymaps, and to facilitate installing and visualizing these keymaps.

Features

  • Inheritance
    • Supports defining keymaps for a given mode by building on top of existing keymaps.
  • Auto-loading
    • emacs-buttons handles auto-installing newly-defined bindings to well-known keymap symbols that may be unbound now and will be loaded in the future, avoiding the need to manually write custom hook functions, eval-after-load, and similar set up code.
  • Code templates
    • Provides a simple but flexible template macro for defining code templates (also known as snippets or keyboard macros).
  • Visualization
    • Each keymap provides a keybinding to help describe itself

Quick start

  • The following defbuttons form
(buttons-macrolet ;; make short aliases available within body
 ((nli () `(newline-and-indent)));; additional aliases
 ;; add the super modifier to all keys in any (but ...) form within this compile-time let-binding
 (let-when-compile ((buttons-make-key-mapper #'buttons-modifier-add-super))
   (defbuttons
     emacs-lisp-buttons;;var name
     nil;; starter keymap
     (emacs-lisp-mode-map read-expression-map inferior-emacs-lisp-mode-map);; target keymaps
     ;; lastly the actual keymap
     (but;; (but (KEY DEF)...) defines an anonymous keymap
      ;; (cmd ...) defines an anonymous command
      ("a" (cmd (ins "(lambda ({}) {})")))
      ;; ins inserts a code template, interpreting {} as directives
      ("z" (cmd (ins "(if {})")))
      ("x" (cmd (ins "(when {})")))
      ("c" (cmd (ins "(unless {})")))
      ("v" (cmd (ins "(progn {})")))
      ("d"
       ;; DEF may be a nested keymap
       (but
        ("v" (cmd (ins "(defvar {}){(nli)}")))
        ("f" (cmd (ins "(defun {} ({}){(nli)}{})")))
        ("m" (cmd (ins "(defmacro {} ({}){(nli)}{})")))
        ("s" (cmd (ins "(defstruct {}{(nli)}{})")))
        ("b" (cmd (ins "(destructuring-bind ({}){})")))))))))

is roughly equivalent to

(defvar emacs-lisp-buttons nil "emacs-lisp-buttons buttons map")

(setq emacs-lisp-buttons
      (let ((kmap434 (make-sparse-keymap)))
        (define-key kmap434 (kbd "s-a") (lambda () (interactive) (insert "(lambda ({}) {})")))
        (define-key kmap434 (kbd "s-z") (lambda () (interactive) (insert "(if {})")))
        (define-key kmap434 (kbd "s-x") (lambda () (interactive) (insert "(when {})")))
        (define-key kmap434 (kbd "s-c") (lambda () (interactive) (insert "(unless {})")))
        (define-key kmap434 (kbd "s-v") (lambda () (interactive) (insert "(progn {})")))
        (define-key kmap434 (kbd "s-d")
          (let
              ((kmap435 (make-sparse-keymap)))
            (define-key kmap435 (kbd "s-?") (lambda () (interactive) (buttons-display kmap435)))
            (define-key kmap435 (kbd "s-v") (lambda () (interactive) (insert "(defvar {}){(nli)}")))
            (define-key kmap435 (kbd "s-f") (lambda () (interactive) (insert "(defun {} ({}){(nli)}{})")))
            (define-key kmap435 (kbd "s-m") (lambda () (interactive) (insert "(defmacro {} ({}){(nli)}{})")))
            (define-key kmap435 (kbd "s-s") (lambda () (interactive) (insert "(defstruct {}{(nli)}{})")))
            (define-key kmap435 (kbd "s-b") (lambda () (interactive) (insert "(destructuring-bind ({}){})")))
            kmap435))
        kmap434))

(add-hook 'emacs-lisp-mode-hook
          (lambda () (setq emacs-lisp-mode-map
                           (make-composed-keymap
                            emacs-lisp-buttons emacs-lisp-mode-map))))

(add-hook 'inferior-emacs-lisp-mode-hook
          (lambda () (setq inferior-emacs-lisp-mode-map
                           (make-composed-keymap
                            emacs-lisp-buttons inferior-emacs-lisp-mode-map))))

(setq read-expression-map
      (make-composed-keymap
       emacs-lisp-buttons read-expression-map))

With some differences:

  • (ins ...) is not plain insert but refers to a code-template-generating macro which interprets directives within {...} brackets
    • other aliases within the scope of buttons-macrolet are (but), (cmd) and (nli)
  • installing the keymap emacs-lisp-buttons onto the specified destination keymaps:
    (emacs-lisp-mode-map read-expression-map inferior-emacs-lisp-mode-map)
        
    • is not done with make-composed-keymap but by recursive merging
    • is not done with add-hook but once for each feature, via after-load-functions

Sample visualization

./doc/img/sample-visualization.png

Installation

  • M-x package-refresh-contents and M-x package-install RET buttons
  • Alternatively

Place buttons.el somewhere in the load-path and require the feature:

(push "/path/to/buttons/parent/directory" load-path)
(require 'buttons)

Overview

defbuttons(KEYMAP-VAR ANCESTOR TARGET-KEYMAPS KEYMAP)

defvar-like wrapper that defines keymap KEYMAP as KEYMAP-VAR.

  • ANCESTOR is a keymap used as a starting point on top of which to add new key bindings.
  • TARGET-KEYMAPS specifies a list of keymap symbols onto which to install KEYMAP-VAR whenever those symbols become bound in emacs after a file load.
  • How inheritance works
    • Placing KEYMAP on top of ANCESTOR, as well as placing the newly-defined KEYMAP-VAR on top of each keymap in TARGET-KEYMAPS as they become available, is done by recursive merging of keymaps via the internal function buttons-define-keymap-onto-keymap, which differs from (set-keymap-parent ...) in that nested keymaps (or bindings for prefix keys) are merged instead of one definition clobbering the other one. This allows a child keymap to both inherit and extend a parent’s nested keymaps

The following example defines a keymap c++buttons using a previously-defined c-buttons as a starting point. The c++-buttons keymap bindings are automatically installed to c++-mode-map whenever that symbol is loaded in emacs.

(defbuttons c++-buttons c-buttons
   (c++-mode-map)
   (let ((kmap (make-sparse-keymap)))
            (define-key kmap (kbd "s-m") (lambda () (interactive) (insert "#include ")))
            kmap))

buttons-make(&rest KEY-TARGET-PAIRS) (aka but)

creates a sparse keymap of bindings specified as (KEY TARGET) pairs.

  • KEY is a key-binding
  • TARGET may be any define-key DEF target, including a command, a plain string, a nested buttons-make form, etc
    (but
    ...
        ((kbd "s-E") #'eval-defun)
        ((kbd "s-i") "(interactive)")
        ((kbd "s-7")
         (but
          ((kbd "s-r") "&rest ")
          ((kbd "s-k") "&key ")
          ((kbd "s-b") "&body ")
          ((kbd "s-o") "&optional ")))
        ...)
        
  • if the variable buttons-make-key-mapper is bound to a function that adds a super modifier, the above form is equivalent to:
    (let-when-compile
        ((buttons-make-key-mapper #'modifier-add-super))
        (but
         ...
         ("E" #'eval-defun)
         ("i" "(interactive)")
         ("7"
          (but
           ("r" "&rest ")
           ("k" "&key ")
           ("b" "&body ")
           ("o" "&optional ")))
         ...))
        

buttons-template-insert (aka ins)

A macro to define a code template.

It it similar to python’s format syntax in that it interprets directives contained within {} braces.

Directives are interpreted as follows:

  • An empty {} prompts the user to enter custom text
  • {N} where N is a number, prompts the user to enter custom text and records it on the first occurrence, and on subsequent occurrences the recorded text is entered without prompt.
  • Anything else within the {...} directive regexp is interpreted as a lisp expression. If the expression evaluates to a string, it is inserted.
  • Example macroexpansion of a typical for-loop:
(macroexpand ' (buttons-template-insert
                  "for ( int {0} = 0; {0} < {}; {0}++ )" (newline-and-indent)))

;; expands to:

(let (rec-capture-0-86054)
  (insert "for ( int ")
  (setf rec-capture-0-86054 (buttons-record-template-var))
  (insert " = 0; ")
  (insert rec-capture-0-86054)
  (insert " < ")
  (recursive-edit)
  (insert "; ")
  (insert rec-capture-0-86054)
  (insert "++ )")
  (let* ((expr-val-86055 (newline-and-indent)))
    (when (stringp expr-val-86055)
      (insert expr-val-86055))))
  • It is possible to change the directive regexp from matching {...} to something else, like <...>, by binding BUTTONS-TEMPLATE-INSERT-DIRECTIVE-REGEXP at compile-time through let-when-compile:
    (let-when-compile ((buttons-template-insert-directive-regexp "<\\(.*?\\)>"))
               ;; insert a bash variable surrounded by double quotes
             (buttons-template-insert "\"${<>}\""))
        
    • A simpler way to achieve the same result is to break up the directive across multiple strings, since a valid directive must be fully contained within one string:
      (macroexpand '(buttons-template-insert "\"${" "{}" "}\"")
      
      =>
      
      (let nil
         (insert "\"${")
         (buttons-record-template-var)
         (insert "}\"")))
              

buttons-defcmd (&rest BODY) (aka cmd)

A convenience macro for defining an auto-documented, auto-named 0-ary command. Used to make frequent use of

(lambda () (interactive) "documentation"...)

look more concise and to provide automatic documentation:

> (buttons-defcmd (message "hello world") (insert "goodbye"))
> autogen-cmd5457
> (describe-function #'autogen-cmd5457)
> ...
  • It also defines a tag that may be thrown to atomically abort the currently executing command. The command buttons-abort-cmd throws this tag.

buttons-macrolet (MORE-MACROLET-DEFS &rest BODY)

Provides short aliases to frequently used functions and macros to make defbuttons forms more concise. Within a buttons-macrolet form, these are default aliases:

shortcutfunction/macro
butbuttons-make
cmdbuttons-defcmd
insbuttons-template-insert
nlinewline-and-indent
cbdbuttons-insert-c-style-code-block
recrecursive-edit
idtindent-for-tab-command
cmtcomint-send-input
cmd-ins(cmd (ins …))

buttons-display

Visualize a keymap. Automatically bound to buttons-make-self-help-binding on all buttons-make-defined keymaps. With a prefix argument, all currently active keymaps are displayed.

Additional links

About

An emacs lisp framework to help organize emacs-lisp commands and code templates into a hierarchy of keymaps, and to facilitate installing and visualizing them

Resources

Stars

Watchers

Forks

Packages

No packages published