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.
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.
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.
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
.
This package is aimed at Common Lisp users but some of the functions it offers works well with Elisp sexps:
if
expressions to cond
expressions.
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-modeEven 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.
Here are some functions I frequently use while doing elisp development:
(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.
(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 :).
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)
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)
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.