isamert.net
About Feeds

Elisp editing/development tips


Here are some workflow tips that I have accumulated over time working with elisp. I try to embrace the interactive nature of developing elisp programs (some may call this REPL-driven development) and you'll see that in the following sections. Tips are generally about editing. There is also this page by alphapapa , that goes really deep into every single topic about elisp development, which I highly recommend you to read.

eval-{last-sexp,region,defun,buffer,expression}

These are the basic evaluation functions that you can use. Having a different binding for each of them might prove to be slightly burdensome, so I generalized eval-{last-sexp,region,defun} into one binding, using the following definition:

(defun im-eval-dwim (lastf regionf defunf)
  "Generate an interactive function that you can bind to a key for evaluating.
The returned function will call LASTF, REGIONF or DEFUNF
depending on the context when called.  If you have an active
region, REGIONF will be called, if you are in middle of an
expression DEFUNF will be called.  If your cursor is near a
closing parenthesis, LASTF will be called."
  (lambda ()
    (interactive)
    (cond
     ((use-region-p)
      (call-interactively regionf))
     ((or (-contains? '(?\) ?\") (char-before))
          (-contains? '(?\ ?\)) (char-after)))
      (call-interactively lastf))
     (t
      (call-interactively defunf)))))

So that I can do:

(bind-key "<key-of-your-choice>" (im-eval-dwim #'eval-last-sexp #'eval-region #'eval-defun))

Upon hitting <key-of-your-choice>, the right eval function (most of the time) will be invoked. I also utilize im-eval-dwim for other lispy languages, like Clojure, Scheme etc. by using their eval counterparts.

Better feedback for evaluation results with eros

If you have used cider for Clojure development (if you didn't, don't worry), you would've seen that it has a delightful overlay that shows the evaluation result near your cursor, instead of at the bottom echo area. To attain the same functionality for elisp, you can use eros package.

Now you can use eros-eval-{last-sexp,defun} functions to evaluate your expressions instead of eval-{last-sexp,defun} to get a nicer feedback. Or simply call (eros-mode) to take eros eval functions in place of original eval functions, but if you decided to use im-eval-dwim, you need to use the following:

(bind-key "<key-of-your-choice>" (im-eval-dwim #'eros-eval-last-sexp #'eval-region #'eros-eval-defun))

Akin to cider-inspect-last-last-result, we have eros-inspect-last-result (see the PR I opened for this feature to get more information. It's already merged). With this, you can inspect the evaluation result in a nicely formatted buffer which makes exploring data really pleasant. The buffer gets updated automatically whenever you evaluate an expression.

M-x ielm

IELM is just a REPL for elisp and I don't really use it that much, given that you can evaluate code anywhere in Emacs. The nice thing about is that, it's good for experimentation sessions where you have a history of the trials you made during the session. Other than this, I have no use for it.

Refactoring & faster editing

emacs-refactor

This package offers lot's of convenient functionality for Elisp, see the features here. I found the context-sensitive menu that pops up when you do M-x emr-show-refactor-menu a bit clumsy. It does not list every action all the time, but you can simply do M-x emr-el- to filter out all Elisp related functionality emacs-refactor offers. Here are some the functions I use frequently: emr-el-extract-function, emr-el-toggle-let*, emr-el-extract-to-let, emr-el-extract-constant, emr-el-extract-variable, emr-el-implement-function.

redshank

This package is aimed at Common Lisp users but some of the functions it offers works well with Elisp sexps:

redshank-condify-form
Converts if expressions to cond expressions.
redshank-enclose-form-with-lambda

Saves you from typing (lambda (x) ...).

(mapcar (* 2 it|) '(1 2 3 4))
;; M-x redshank-enclose-form-with-lambda RET RET
(mapcar (lambda (it) (* 2 it)) '(1 2 3 4))

clojure-thread-{last,first}-all from clojure-mode

Even though it's for Clojure, works well with Elisp sexps.

Converts your expression into a threaded expression. Assume that you have written something horrible like this (which I did):

(s-chop-suffix "]" (s-chop-prefix "[" (substring-no-properties (car (s-match "\\[.*?\\]" (org-get-heading))))))

Calling clojure-thread-last-all at the beginning this expression will turn it into:

(->>
 (org-get-heading)
 (s-match "\\[.*?\\]")
 car
 substring-no-properties
 (s-chop-prefix "[")
 (s-chop-suffix "]"))

Which (I believe) is nicer. You can replace ->> with thread-last if you want to use the built-in threading function instead of dash.el provided one.

If you are a lispy user, it also offers similar functionality. I don't use structural editing tools much because I am an evil user.

clojure-mode has lots of functionality like this but they do not play well with elisp. But aforementioned emacs-refactor and redshank does the job.

Supplementary functions

Here are some functions I frequently use while doing elisp development:

im-tap

(defmacro im-tap (form)
  "Evaluate FORM and return its result.
Additionally, print a message to the *Messages* buffer showing
the form and its result.

This macro is useful for debugging and inspecting the
intermediate results of Elisp code without changing your code
structure. Just wrap the form with `im-tap' that you want to see
it's output without introducing an intermediate let-form."
  `(let ((result ,form))
     (message "[im-tap :: %s] → %s" ,(prin1-to-string form) result)
     result))

See the following for it's usefulness:

;; I want to print-debug those input values

(defun test ()
  (final-func (func-a 1 2) (func-b 3 4)))

;; Normally I have to introduce a let-binding

(defun test ()
  (let ((a (func-a 1 2))
        (b (func-b 3 4)))
    (message "func-a: %s" a)
    (message "func-b: %s" b)
    (final-func a b)))

;; But with im-tap, I can do

(defun test ()
  (final-func (im-tap (func-a 1 2)) (im-tap (func-b 3 4))))

;; Also useful for threading situations

(->>
 (org-get-heading)
 (s-match "\\[.*?\\]")
 car
 substring-no-properties
 im-tap ;; → Print the result so far
 (s-chop-prefix "[")
 (s-chop-suffix "]"))

It also prints the form itself with the value the form returns, so that you can use it multiple times and not get confused which printed value belongs to which form.

im-debug

(defun im-debug (thing)
  "Like `im-tap' but uses `pp-display-expression' to display the
result instead of `message'."
  (pp-display-expression thing "*im-debug*")
  thing)

This also works like im-tap, so that you can simply wrap a form with it and the code continues to work. This is useful for long outputs that you want to inspect.

Also useful inside an interactive eval-expression call. Does the same thing as calling pp-display-expression but easier to type :).

Other helpful packages

suggest
Enter your inputs and your desired output. It will suggest you some elisp functions to achieve that. I quite frequently use this because I forget most basic functions all the time.
highlight-quoted
Simply highlights quotes and symbols. Makes reading easier and feels natural.
hs-minor-mode

Built-in mode for folding. It works fairly well with elisp code. Evil mode is also automatically integrates itself with it, so that you can use folding bindings (za zm zr etc.)

(add-hook 'emacs-lisp-mode-hook #'hs-minor-mode)
(add-hook 'lisp-interaction-mode-hook #'hs-minor-mode)
aggressive-indent-mode

Aggressively indents your code. I found it to be less useful for other languages (which I generally use an auto-formatter on save kinda solution) but for lisp, it works fairly well and I can't do without it.

(add-hook 'emacs-lisp-mode-hook #'aggressive-indent-mode)
;; If you are an evil user, this might be useful:
(add-to-list 'aggressive-indent-protected-commands 'evil-undo)
helpful
Better help pages.
elisp-demos
Code example for elisp functions in help pages. Integrates with helpful.

Automatic indentation fix

Automatic indentation works fairly well but it fails on quoted lists. I use Fuco1's solution, just copied that into my configuration verbatim. Here is the illustration of what it fixes:

;; before
;;   (:foo bar
;;         :baz qux)
;; after
;;   (:foo bar
;;    :baz qux)

Especially useful if you are a user of aggressive-indent-mode that I've talked about above.

Similar posts