#+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. I’m 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
Wilfred’s [[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 don’t have to learn anything, 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 /defined in the org file/. Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, I’ll 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)

    :config
    (sp-with-modes sp-lisp-modes
      ;; disable ', as it's the quote character:
      (sp-local-pair "'" nil :actions nil))

    (sp-with-modes (-difference sp-lisp-modes sp-clojure-modes)
      ;; use the pseudo-quote inside strings where it serve as hyperlink.
      (sp-local-pair "`" "'"
                     :when '(sp-in-string-p
                             sp-in-comment-p)
                     :skip-match (lambda (ms _mb _me)
                                   (cond
                                    ((equal ms "'") (not (sp-point-in-string-or-comment)))
                                    (t (not (sp-point-in-string-or-comment)))))))
    :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. Let’s 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