diff --git a/bootstrap.org b/bootstrap.org index ffbc226..2446731 100644 --- a/bootstrap.org +++ b/bootstrap.org @@ -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-remoting.org" "ha-programming.org" + "ha-programming-elisp.org" "ha-programming-python.org" ,(if (ha-emacs-for-work?) '("ha-org-sprint.org" "ha-work.org") diff --git a/ha-programming-elisp.org b/ha-programming-elisp.org new file mode 100644 index 0000000..6262b7b --- /dev/null +++ b/ha-programming-elisp.org @@ -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 + ;; 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 diff --git a/ha-programming.org b/ha-programming.org index 7cb8b3c..dba35a1 100644 --- a/ha-programming.org +++ b/ha-programming.org @@ -399,198 +399,6 @@ The [[https://github.com/emacsmirror/poly-ansible][poly-ansible]] project uses [ :hook (yaml-mode . poly-ansible-mode)) #+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 '(("" 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 "") 'my/lispy-cheat-sheet/body)) -#+END_SRC - ** Shell Scripts While I don't like writing them, I can't get away from them. diff --git a/snippets/emacs-lisp-mode/general-key b/snippets/emacs-lisp-mode/general-key new file mode 100644 index 0000000..c522b8a --- /dev/null +++ b/snippets/emacs-lisp-mode/general-key @@ -0,0 +1,7 @@ +# -*- mode: snippet -*- +# name: general-key +# key: gk +# -- +:general +(:states 'normal :keymaps '${1:global}-mode-map + "$2" '$0) \ No newline at end of file