Strict Emacs Lisp mode
Finally. Plus I thought I would move these stuff to its own file.
This commit is contained in:
parent
b7b5d1aada
commit
9f0de0db6f
4 changed files with 176 additions and 192 deletions
|
@ -204,6 +204,7 @@ The following loads the rest of my org-mode literate files. I add them as they a
|
||||||
"ha-passwords.org"
|
"ha-passwords.org"
|
||||||
"ha-remoting.org"
|
"ha-remoting.org"
|
||||||
"ha-programming.org"
|
"ha-programming.org"
|
||||||
|
"ha-programming-elisp.org"
|
||||||
"ha-programming-python.org"
|
"ha-programming-python.org"
|
||||||
,(if (ha-emacs-for-work?)
|
,(if (ha-emacs-for-work?)
|
||||||
'("ha-org-sprint.org" "ha-work.org")
|
'("ha-org-sprint.org" "ha-work.org")
|
||||||
|
|
168
ha-programming-elisp.org
Normal file
168
ha-programming-elisp.org
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
#+TITLE: Emacs Lisp Configuration
|
||||||
|
#+AUTHOR: Howard X. Abrams
|
||||||
|
#+DATE: 2022-05-11
|
||||||
|
#+FILETAGS: :emacs:
|
||||||
|
|
||||||
|
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
|
||||||
|
;; This work is 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. So 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
|
||||||
|
I'm going to play with the [[https://github.com/DogLooksGood/parinfer-mode][parinfer]] package.
|
||||||
|
|
||||||
|
* Navigation and Editing
|
||||||
|
** 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
|
||||||
|
(+ 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
|
||||||
|
(+ 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
|
||||||
|
(+ 41 ‖(* 1 3)) ⟹ (+ ‖(41 * 1 3))
|
||||||
|
#+END_SRC
|
||||||
|
And the ~>~ moves the paren to the right. For instance:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(+ 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
|
||||||
|
(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
|
||||||
|
(‖ (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
|
||||||
|
|
||||||
|
A feature I enjoyed from Spacemacs is the ability to evaluate the s-expression currently containing the point. Not sure how how they made it, but 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
|
|
@ -399,198 +399,6 @@ The [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project uses [
|
||||||
:hook (yaml-mode . poly-ansible-mode))
|
:hook (yaml-mode . poly-ansible-mode))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Emacs Lisp
|
|
||||||
|
|
||||||
Why yes, I do find I code a lot in Emacs…
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(ha-auto-insert-file (rx ".el" eol) "emacs-lisp-mode.el")
|
|
||||||
#+END_SRC
|
|
||||||
However, most of my Emacs Lisp code is in literate org files.
|
|
||||||
|
|
||||||
*** Clever Parenthesis
|
|
||||||
|
|
||||||
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-mode
|
|
||||||
:custom
|
|
||||||
evil-cleverparens-use-additional-bindings t
|
|
||||||
evil-cleverparens-use-additional-movement-keys t
|
|
||||||
evil-cleverparens-use-s-and-S nil ; using evil-sniper
|
|
||||||
|
|
||||||
:init
|
|
||||||
(require 'evil-cleverparens-text-objects)
|
|
||||||
|
|
||||||
:hook (prog-mode . evil-cleverparens-mode)) ;; All the languages!
|
|
||||||
;; Otherwise: (emacs-lisp-mode . evil-cleverparens-mode)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
I would like to have a list of what keybindings do what:
|
|
||||||
- ~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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
A feature I enjoyed from Spacemacs is the ability to evaluate the s-expression currently containing the point. Not sure how how they made it, but 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. The following is Doom-specific:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(ha-prog-leader
|
|
||||||
"e c" '("current" . ha-eval-current-expression))
|
|
||||||
#+END_SRC
|
|
||||||
*** 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
|
|
||||||
I'm going to play with the [[https://github.com/DogLooksGood/parinfer-mode][parinfer]] package.
|
|
||||||
*** Lispy
|
|
||||||
Sacha had an [[https://sachachua.com/blog/2021/04/emacs-making-a-hydra-cheatsheet-for-lispy/][interesting idea]] to /generate/ a Hydra from a mode map:
|
|
||||||
|
|
||||||
#+NAME: bindings
|
|
||||||
| key | function | column |
|
|
||||||
|-----+-------------------------------+----------|
|
|
||||||
| < | lispy-barf | |
|
|
||||||
| A | lispy-beginning-of-defun | |
|
|
||||||
| j | lispy-down | |
|
|
||||||
| Z | lispy-edebug-stop | |
|
|
||||||
| B | lispy-ediff-regions | |
|
|
||||||
| G | lispy-goto-local | |
|
|
||||||
| h | lispy-left | |
|
|
||||||
| N | lispy-narrow | |
|
|
||||||
| y | lispy-occur | |
|
|
||||||
| o | lispy-other-mode | |
|
|
||||||
| J | lispy-outline-next | |
|
|
||||||
| K | lispy-outline-prev | |
|
|
||||||
| P | lispy-paste | |
|
|
||||||
| l | lispy-right | |
|
|
||||||
| I | lispy-shifttab | |
|
|
||||||
| > | lispy-slurp | |
|
|
||||||
| SPC | lispy-space | |
|
|
||||||
| xB | lispy-store-region-and-buffer | |
|
|
||||||
| u | lispy-undo | |
|
|
||||||
| k | lispy-up | |
|
|
||||||
| v | lispy-view | |
|
|
||||||
| V | lispy-visit | |
|
|
||||||
| W | lispy-widen | |
|
|
||||||
| D | pop-tag-mark | |
|
|
||||||
| x | see | |
|
|
||||||
| L | unbound | |
|
|
||||||
| U | unbound | |
|
|
||||||
| X | unbound | |
|
|
||||||
| Y | unbound | |
|
|
||||||
| H | lispy-ace-symbol-replace | Edit |
|
|
||||||
| c | lispy-clone | Edit |
|
|
||||||
| C | lispy-convolute | Edit |
|
|
||||||
| n | lispy-new-copy | Edit |
|
|
||||||
| O | lispy-oneline | Edit |
|
|
||||||
| r | lispy-raise | Edit |
|
|
||||||
| R | lispy-raise-some | Edit |
|
|
||||||
| \ | lispy-splice | Edit |
|
|
||||||
| S | lispy-stringify | Edit |
|
|
||||||
| i | lispy-tab | Edit |
|
|
||||||
| xj | lispy-debug-step-in | Eval |
|
|
||||||
| xe | lispy-edebug | Eval |
|
|
||||||
| xT | lispy-ert | Eval |
|
|
||||||
| e | lispy-eval | Eval |
|
|
||||||
| E | lispy-eval-and-insert | Eval |
|
|
||||||
| xr | lispy-eval-and-replace | Eval |
|
|
||||||
| p | lispy-eval-other-window | Eval |
|
|
||||||
| q | lispy-ace-paren | Move |
|
|
||||||
| z | lispy-knight | Move |
|
|
||||||
| s | lispy-move-down | Move |
|
|
||||||
| w | lispy-move-up | Move |
|
|
||||||
| t | lispy-teleport | Move |
|
|
||||||
| Q | lispy-ace-char | Nav |
|
|
||||||
| - | lispy-ace-subword | Nav |
|
|
||||||
| a | lispy-ace-symbol | Nav |
|
|
||||||
| b | lispy-back | Nav |
|
|
||||||
| d | lispy-different | Nav |
|
|
||||||
| f | lispy-flow | Nav |
|
|
||||||
| F | lispy-follow | Nav |
|
|
||||||
| g | lispy-goto | Nav |
|
|
||||||
| xb | lispy-bind-variable | Refactor |
|
|
||||||
| xf | lispy-flatten | Refactor |
|
|
||||||
| xc | lispy-to-cond | Refactor |
|
|
||||||
| xd | lispy-to-defun | Refactor |
|
|
||||||
| xi | lispy-to-ifs | Refactor |
|
|
||||||
| xl | lispy-to-lambda | Refactor |
|
|
||||||
| xu | lispy-unbind-variable | Refactor |
|
|
||||||
| M | lispy-multiline | Other |
|
|
||||||
| xh | lispy-describe | Other |
|
|
||||||
| m | lispy-mark-list | Other |
|
|
||||||
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :var bindings=bindings :colnames yes :tangle no
|
|
||||||
(defvar my-lispy-bindings bindings)
|
|
||||||
|
|
||||||
(defvar ha-hydra-lispy-bindings
|
|
||||||
(cl-loop for x in my-lispy-bindings
|
|
||||||
unless (string= "" (elt x 2))
|
|
||||||
collect
|
|
||||||
(list (car x)
|
|
||||||
(intern (elt x 1))
|
|
||||||
(when (string-match "lispy-\\(?:eval-\\)?\\(.+\\)"
|
|
||||||
(elt x 1))
|
|
||||||
(match-string 1 (elt x 1)))
|
|
||||||
:column
|
|
||||||
(elt x 2)))
|
|
||||||
"Collection of memorable Lispy functions")
|
|
||||||
|
|
||||||
(eval
|
|
||||||
`(defhydra
|
|
||||||
,(append '(("<f14>" nil :exit t)) ha-hydra-lispy-bindings )
|
|
||||||
|
|
||||||
))
|
|
||||||
(funcall defhydra
|
|
||||||
`(my/lispy-cheat-sheet (:hint nil :foreign-keys run)
|
|
||||||
))
|
|
||||||
(with-eval-after-load "lispy"
|
|
||||||
(define-key lispy-mode-map (kbd "<f14>") 'my/lispy-cheat-sheet/body))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Shell Scripts
|
** Shell Scripts
|
||||||
|
|
||||||
While I don't like writing them, I can't get away from them.
|
While I don't like writing them, I can't get away from them.
|
||||||
|
|
7
snippets/emacs-lisp-mode/general-key
Normal file
7
snippets/emacs-lisp-mode/general-key
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- mode: snippet -*-
|
||||||
|
# name: general-key
|
||||||
|
# key: gk
|
||||||
|
# --
|
||||||
|
:general
|
||||||
|
(:states 'normal :keymaps '${1:global}-mode-map
|
||||||
|
"$2" '$0)
|
Loading…
Reference in a new issue