I created an interactive wrapper.
10 KiB
Emacs Lisp Configuration
A literate programming file for configuring Emacs for Lisp programming.
Introduction
While I program in a lot of languages, I seem to be writing all my helper tools and scripts in … Emacs Lisp. So I’m cranking this up to 11.
New, non-literal source code comes from emacs-lisp-mode template:
(ha-auto-insert-file (rx ".el" eol) "emacs-lisp-mode.el")
Syntax Display
Dim those Parenthesis
The paren-face project lowers the color level of parenthesis which I find better.
(use-package paren-face
:hook (emacs-lisp-mode . paren-face-mode))
Show code examples with the elisp-demos package. This is really helpful.
(use-package elisp-demos
:config
(advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1))
Navigation and Editing
Goto Definitions
Wilfred’s elisp-def project does a better job at jumping to the definition of a symbol at the point, so:
(use-package elisp-def
:hook (emacs-lisp-mode . elisp-def-mode))
This should work with evil-goto-defintion, as that calls this list from evil-goto-definition-functions:
- evil-goto-definition-imenu
- evil-goto-definition-semantic
- evil-goto-definition-xref … and here is where this package will be called
- evil-goto-definition-search
I love packages that add functionality but I don’t have to learn anything. However, I’m running into an issue where I do a lot of my Emacs Lisp programming in org files, and would like to jump to the function definition where it is defined in the org file. Since ripgrep is pretty fast, I’ll call it instead of attempting to build a CTAGS table. Oooh, the rg
takes a —json
option, which makes it easier to parse.
(defun ha-org-code-block-jump (str pos)
"Go to a literate org file containing a symbol, STR.
The POS is ignored."
;; Sometimes I wrap a function name in `=' characters, and these should be removed:
(when (string-match (rx "=" (group (one-or-more any)) "=") str)
(setq str (match-string 1 str)))
(ignore-errors
(let* ((default-directory (projectile-project-root))
(command (format "rg --json '\\(def[^ ]+ %s ' *.org" str))
(results (thread-last command
shell-command-to-list
second
json-parse-string))
(file (thread-last results
(gethash "data")
(gethash "path")
(gethash "text")))
(line (thread-last results
(gethash "data")
(gethash "line_number"))))
(find-file file)
(goto-line line))))
(add-to-list 'evil-goto-definition-functions 'ha-org-code-block-jump)
And in case I need to call it directly:
(defun ha-goto-definition ()
(interactive)
(evil-inner-WORD))
Clever Parenthesis
We need to make sure we keep the smartparens project always in strict mode, because who wants to worry about paren-matching:
(use-package smartparens
:custom
(smartparens-global-strict-mode t)
:hook
(prog-mode . smartparens-strict-mode))
The evil-cleverparens solves having me create keybindings to the smartparens project by updating the evil states with Lisp-specific bindings.
(use-package evil-cleverparens
:after smartparens
:custom
(evil-cleverparens-use-additional-bindings t)
(evil-cleverparens-use-additional-movement-keys t)
(evil-cleverparens-use-s-and-S t)
:init
(require 'evil-cleverparens-text-objects)
:hook
(prog-mode . evil-cleverparens-mode)) ;; All the languages!
;; Otherwise: (emacs-lisp-mode . evil-cleverparens-mode)
The trick to being effective with the paredit-family of extensions is learning the keys. The killer “app” is the slurp/barf sequence. Use the <
key, in normal mode, to barf (or jettison)… in other words, move the paren closer to the point. For instance:
(+ 41 (* ‖1 3)) ⟹ (+ 41 (* ‖1) 3)
Use the >
key to slurp in outside objects into the current expression… in other words, move the paren away from the point. For instance:
(+ 41 (* ‖1) 3) ⟹ (+ 41 (* ‖1 3))
Opening Parens. Those two keys seem straight-forward, but they behave differently when the are on the opening parens.
When the point (symbolized by ‖
) is on the opening paren, <
moves the paren to the left. For instance:
(+ 41 ‖(* 1 3)) ⟹ (+ ‖(41 * 1 3))
And the >
moves the paren to the right. For instance:
(+ 41 ‖(* 1 3)) ⟹ (+ 41 * ‖(1 3))
I would like to have a list of what keybindings that work in normal
mode:
M-h
/M-l
move back/forward by functionsH
/L
move back/forward by s-expressionM-i
insert at the beginning of the formM-a
appends at the end of the formM-o
new form after the current sexpM-O
new form before the current sexpM-j
/M-k
drags thing at point and back and forth in the form>
slurp forward if at the end of form, at beginning, it barfs backwards<
slurp backward if at start of form, at the end, it barfs forwardsM-(
/M-)
wraps next/previous form in parens (braces and brackets work too)x
unwraps if the point is on the(
of an expression.D
deletes an entire s-expression, but this can depend on the position of the point.
The other advantage is moving around by s-expressions. This takes a little getting used to, for instance:
[
and]
move from paren to paren, essentially, from s-expression.H
andL
act similarly to the above.(
and)
move up to the parent s-expression
We need a real-world example. Let’s suppose we entered this:
(format "The sum of %d %d is %d" a b (+ a b))
But we forgot to define the a
and b
variables. One approach, after Escaping into the normal state, is to hit (
to just to the beginning of the s-expression, and then type, M-(
to wrap the expression, and type i
to go into insert mode:
(‖ (format "The sum of %d %d is %d" a b (+ a b)))
And now we can enter the let
expression.
Other nifty keybindings that I need to commit to muscle memory include:
M-q |
sp-indent-defun |
M-J |
sp-join-sexp |
M-s |
sp-splice-sexp |
M-S |
sp-split-sexp |
M-t |
sp-transpose-sexp |
M-v |
sp-convolute-sexp |
M-r |
sp-raise-sexp |
Eval Current Expression
The eros package stands for Evaluation Result OverlayS for Emacs Lisp, and basically shows what each s-expression is near the cursor position instead of in the mini-buffer at the bottom of the window.
(use-package eros
:hook (emacs-lisp-mode . eros-mode))
A feature I enjoyed from Spacemacs is the ability to evaluate the s-expression currently containing the point. Not sure how they made it, but evil-cp-next-closing from cleverparens can help:
(defun ha-eval-current-expression ()
"Evaluates the expression the point is currently 'in'.
It does this, by jumping to the end of the current
expression (using evil-cleverparens), and evaluating what it
finds at that point."
(interactive)
(save-excursion
(evil-cp-next-closing)
(evil-cp-forward-sexp)
(call-interactively 'eval-last-sexp)))
And we just need to bind it.
(ha-prog-leader
"e c" '("current" . ha-eval-current-expression))