hamacs/ha-programming-elisp.org
Howard Abrams ffbd253e65 Convert to lower-case #+BEGIN_SRC blocks
While I was at it, I address some prose-specific comments like passive
sentences and weasel words.
2022-06-17 17:25:47 -07:00

223 lines
10 KiB
Org Mode
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: Emacs Lisp Configuration
#+AUTHOR: Howard X. Abrams
#+DATE: 2022-05-11
A literate programming file for configuring Emacs for Lisp programming.
#+begin_src emacs-lisp :exports none
;;; ha-lisp --- configuring Emacs for Lisp programming. -*- lexical-binding: t; -*-
;;
;; © 2022 Howard X. Abrams
;; Licensed under a Creative Commons Attribution 4.0 International License.
;; See http://creativecommons.org/licenses/by/4.0/
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams
;; Created: May 11, 2022
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /Users/howard.abrams/other/hamacs/ha-lisp.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+end_src
* Introduction
While I program in a lot of languages, I seem to be writing all my helper tools and scripts in … Emacs Lisp. Im cranking this up to 11.
New, /non-literal/ source code comes from [[file:templates/emacs-lisp-mode.el][emacs-lisp-mode template]]:
#+begin_src emacs-lisp
(ha-auto-insert-file (rx ".el" eol) "emacs-lisp-mode.el")
#+end_src
* Syntax Display
** Dim those Parenthesis
The [[https://github.com/tarsius/paren-face][paren-face]] project lowers the color level of parenthesis which I find better.
#+begin_src emacs-lisp
(use-package paren-face
:hook (emacs-lisp-mode . paren-face-mode))
#+end_src
Show code examples with the [[https://github.com/xuchunyang/elisp-demos][elisp-demos]] package.
#+begin_src emacs-lisp
(use-package elisp-demos
:config
(advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1))
#+end_src
* Navigation and Editing
** Goto Definitions
Wilfreds [[https://github.com/Wilfred/elisp-def][elisp-def]] project does a better job at jumping to the definition of a symbol at the point, so:
#+begin_src emacs-lisp
(use-package elisp-def
:hook (emacs-lisp-mode . elisp-def-mode))
#+end_src
This /should work/ with [[help:evil-goto-definition][evil-goto-defintion]], as that calls this list from [[help:evil-goto-definition-functions][evil-goto-definition-functions]]:
- [[help:evil-goto-definition-imenu][evil-goto-definition-imenu]]
- [[help:evil-goto-definition-semantic][evil-goto-definition-semantic]]
- [[help:evil-goto-definition-xref][evil-goto-definition-xref]] … to show what calls a function
- [[help:evil-goto-definition-search][evil-goto-definition-search]]
While I love packages that add functionality and I dont have to learn anything, Im 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 /defined in the org file/. Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, Ill call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse.
#+begin_src emacs-lisp
(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)
#+end_src
And in case I need to call it directly:
#+begin_src emacs-lisp
(defun ha-goto-definition ()
(interactive)
(evil-inner-WORD))
#+end_src
** Clever Parenthesis
We need to make sure we keep the [[https://github.com/Fuco1/smartparens][smartparens]] project always in /strict mode/, because who wants to worry about paren-matching:
#+begin_src emacs-lisp
(use-package smartparens
:custom
(smartparens-global-strict-mode t)
:hook
(prog-mode . smartparens-strict-mode))
#+end_src
The [[https://github.com/luxbock/evil-cleverparens][evil-cleverparens]] solves having me create keybindings to the [[https://github.com/Fuco1/smartparens][smartparens]] project by updating the evil states with Lisp-specific bindings.
#+begin_src emacs-lisp
(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)
#+end_src
The /trick/ to being effective with the [[https://www.emacswiki.org/emacs/ParEdit][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:
#+begin_src emacs-lisp :tangle no
(+ 41 (* 1 3)) (+ 41 (* 1) 3)
#+end_src
Use the ~>~ key to /slurp/ in outside objects into the current expression… in other words, move the paren away from the point. For instance:
#+begin_src emacs-lisp :tangle no
(+ 41 (* 1) 3) (+ 41 (* 1 3))
#+end_src
*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:
#+begin_src emacs-lisp :tangle no
(+ 41 (* 1 3)) (+ (41 * 1 3))
#+end_src
And the ~>~ moves the paren to the right. For instance:
#+begin_src emacs-lisp :tangle no
(+ 41 (* 1 3)) (+ 41 * (1 3))
#+end_src
I would like to have a list of what keybindings that work in =normal= mode:
- ~M-h~ / ~M-l~ move back/forward by functions
- ~H~ / ~L~ move back/forward by s-expression
- ~M-i~ insert at the beginning of the form
- ~M-a~ appends at the end of the form
- ~M-o~ new form after the current sexp
- ~M-O~ new form /before/ the current sexp
- ~M-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 forwards
- ~M-(~ / ~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~ and ~L~ act similarly to the above.
- ~(~ and ~)~ move up to the parent s-expression
We need a real-world example. Lets suppose we entered this:
#+begin_src emacs-lisp :tangle no
(format "The sum of %d %d is %d" a b (+ a b))
#+end_src
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:
#+begin_src emacs-lisp :tangle no
( (format "The sum of %d %d is %d" a b (+ a b)))
#+end_src
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 [[https://github.com/xiongtx/eros][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.
#+begin_src emacs-lisp
(use-package eros
:hook (emacs-lisp-mode . eros-mode))
#+end_src
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 [[help:evil-cp-next-closing ][evil-cp-next-closing]] from cleverparens can help:
#+begin_src emacs-lisp
(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)))
#+end_src
And we just need to bind it.
#+begin_src emacs-lisp
(ha-prog-leader
"e c" '("current" . ha-eval-current-expression))
#+end_src
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+begin_src emacs-lisp :exports none
(provide 'ha-programming-elisp)
;;; ha-programming-elisp.el ends here
#+end_src
#+DESCRIPTION: configuring Emacs for Lisp programming.
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp :tangle yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js