From 815f86fd871c8dd810c538f57410d3065fddc6e2 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Fri, 22 Mar 2024 13:43:37 -0700 Subject: [PATCH] Better Lisp Editing with major-mode-hydra --- ha-general.org | 4 +- ha-programming-elisp.org | 102 ++++++++++++++++++++++----------------- ha-programming.org | 22 ++++----- 3 files changed, 70 insertions(+), 58 deletions(-) diff --git a/ha-general.org b/ha-general.org index 731810a..b39d8b1 100644 --- a/ha-general.org +++ b/ha-general.org @@ -42,11 +42,11 @@ I'm not trying an experiment where specially-placed function keys on my fancy er :keymaps 'override :prefix "SPC" :non-normal-prefix "M-SPC" - :prefix "") + :global-prefix "") (general-create-definer ha-local-leader :states '(normal visual motion) - :global-prefix "") + :prefix "") (general-nmap "SPC m" (general-simulate-key "," :which-key "major mode"))) #+end_src diff --git a/ha-programming-elisp.org b/ha-programming-elisp.org index 18db519..a7bc7af 100644 --- a/ha-programming-elisp.org +++ b/ha-programming-elisp.org @@ -123,32 +123,35 @@ I like the idea of [[https://github.com/abo-abo/lispy][lispy]] for making a Lisp My primary use-case is for its refactoring and other unique features. For instance, I love [[help:lispy-ace-paren][lispy-ace-paren]] that puts an /ace label/ on every parenthesis, allowing me to quickly jump to any s-expression. #+begin_src emacs-lisp - (use-package lispy - :config - (when (fboundp 'evil-define-key) - (evil-define-key '(normal visual) lispyville-mode-map - ;; Jump to interesting places: - "gf" '("ace paren" . lispy-ace-paren) - "gF" '("ace symbol" . lispy-ace-symbol) - (kbd "M-v") '("mark s-exp" . lispy-mark))) ; Mark entire s-expression + (use-package lispy + :config + (when (fboundp 'evil-define-key) + (evil-define-key '(normal visual) lispyville-mode-map + ;; Jump to interesting places: + "gf" '("ace paren" . lispy-ace-paren) + "gF" '("ace symbol" . lispy-ace-symbol) + (kbd "M-v") '("mark s-exp" . lispy-mark))) ; Mark entire s-expression - (ha-local-leader :keymaps '(emacs-lisp-mode-map lisp-mode-map) - "r" '(:ignore t :which-key "refactor") - "r i" '("cond→if" . lispy-to-ifs) - "r c" '("if→cond" . lispy-to-cond) - "r d" '("λ→𝑓" . lispy-to-defun) - "r l" '("𝑓→λ" . lispy-to-lambda) - "r f" '("flatten" . lispy-flatten) - "r b" '("bind var" . lispy-bind-variable) - "r u" '("unbind var" . lispy-unbind-variable) - "r >" '("to thread last" . lispy-toggle-thread-last) + (pretty-hydra-define lispy-debug nil + ("Debug" + (("d" lispy-edebug "Start") + ("j" lispy-debug-step-in "Jump in") + ("r" lispy-eval-and-replace "Eval/Replace")) + "Instrument" + (("f" (eval-defun t) "Function")) + )) - "e d" '("edebug" . lispy-edebug) - "e j" '("debug-step-in" . lispy-debug-step-in) - "e R" '("eval-and-replace" . lispy-eval-and-replace) - - "d d" '("describe" . lispy-describe) - "t t" '("ert" . lispy-ert))) + (pretty-hydra-define lisp-refactor nil + ("To" + (("i" lispy-to-ifs "cond→if") + ("c" lispy-to-cond "if→cond") + ("t" lispy-toggle-thread-last "to thread") + ("d" lispy-to-defun "λ→𝑓") + ("l" lispy-to-lambda "𝑓→λ")) + "Convert" + (("F" lispy-flatten "flatten") + ("b" lispy-bind-variable "bind var") + ("B" lispy-unbind-variable "unbind var"))))) #+end_src ** Lispyville I want an Evil version of [[Lispy]]. The [[https://github.com/noctuid/lispyville][lispyville project]] builds on it to make it Evil. From the README: @@ -273,21 +276,22 @@ These are all good, but the primary keys I need to figure out, are the s-express ** Refactoring Wilfred’s [[https://github.com/Wilfred/emacs-refactor/tree/master#elisp][emacs-refactor]] package can be helpful if you turn on =context-menu-mode= and … #+begin_src emacs-lisp - (use-package emacs-refactor - :general - (:states '(normal visual) :keymaps 'emacs-lisp-mode-map - ;; Often know what functions are available: - ", r r" '("refactor menu" . emr-show-refactor-menu) - ;; These are my favorites ... + (use-package emr + ;; :straight (:host github :repo "Wilfred/emacs-refactor") + :config + (pretty-hydra-define+ lisp-refactor nil + ("To 𝛌" + (;; Often know what functions are available: + ("a" emr-show-refactor-menu "all") + ;; Extracts the current s-expression or region to function: + ("f" emr-el-extract-function "to function") + ("v" emr-el-extract-variable "to variable") + ;; Converts the current let to a let* + ("*" emr-el-toggle-let* "toggle let*") + ;; asks for a variable, and extracts the code in a region + ;; or the current s-expression, into the nearest let binding + ("L" emr-el-extract-to-let "to let"))))) - ;; Extracts the current s-expression or region to function: - ", r F" '("to function" . emr-el-extract-function) - ", r V" '("to variable" . emr-el-extract-variable) - ;; Converts the current let to a let* - ", r *" '("toggle let*" . emr-el-toggle-let*) - ;; asks for a variable, and extracts the code in a region - ;; or the current s-expression, into the nearest let binding - ", r l" '("to let" . emr-el-extract-to-let))) #+end_src The idea of stealing some of Clojure Mode’s refactoring is brilliant (see [[https://isamert.net/2023/08/14/elisp-editing-development-tips.html#clojure-thread-lastfirst-all-from-https-github-com-clojure-emacs-clojure-mode-clojure-mode][the original idea]]), however, I’m already using Lispy’s =toggle-thread-last=. @@ -332,14 +336,22 @@ And we just need to bind it. (ha-local-leader :keymaps '(emacs-lisp-mode-map lisp-mode-map) "e e" '("current" . ha-eval-current-expression)) #+end_src -** Debugging -The =edebug= debugger is built into Emacs, so all I need is an easier way to instrument a function: +* Minor Mode Hydra +put it all together #+begin_src emacs-lisp - (ha-local-leader :keymaps '(emacs-lisp-mode-map lisp-mode-map) - "e D" '("set edebug" . (lambda () - (interactive) - (setq current-prefix-arg '(4)) ; C-u - (call-interactively 'eval-defun)))) + (use-package major-mode-hydra + :config + (major-mode-hydra-define emacs-lisp-mode nil + ("Evaluating" + (("e" ha-eval-current-expression "Current") + ("d" lispy-debug/body "Debugging") + ("f" eval-defun "Function") + ("b" eval-buffer "Buffer")) + "Editing" + (("r" lisp-refactor/body "Refactoring")) + "Documentation" + (("a" elisp-demos-add-demo "Add Demo") + ("H" suggest "Suggestions"))))) #+end_src * Technical Artifacts :noexport: Let's =provide= a name so we can =require= this file: diff --git a/ha-programming.org b/ha-programming.org index 368c211..85bee25 100644 --- a/ha-programming.org +++ b/ha-programming.org @@ -103,18 +103,18 @@ I’m interested in using [[https://devdocs.io/][devdocs]] instead, which is sim #+begin_src emacs-lisp (use-package devdocs :general (:states 'normal - "gD" '("devdocs" . devdocs-lookup)) + "gD" '("devdocs" . devdocs-lookup)) :config - (ha-local-leader :keymaps 'prog-mode-map - "d" '(:ignore t :which-key "docs") - "d e" '("eldoc" . eldoc) - "d d" '("open" . devdocs-lookup) - "d p" '("peruse" . devdocs-peruse) - "d i" '("install" . devdocs-install) - "d u" '("update" . devdocs-update-all) - "d x" '("uninstall" . devdocs-delete) - "d s" '("search" . devdocs-search))) + (major-mode-hydra-define emacs-lisp-mode nil + ("Dev Docs" + (("e" eldoc "eldoc") + ("d" devdocs-lookup "open") + ("p" devdocs-peruse "peruse") + ("i" devdocs-install "install") + ("u" devdocs-update-all "update") + ("x" devdocs-delete "uninstall") + ("s" devdocs-search "search"))))) #+end_src The [[https://github.com/blahgeek/emacs-devdocs-browser][devdocs-browser]] project acts similar, but with slightly different command names. Its advantage is that it allows for downloading docs and having it available offline, in fact, you can’t search for a function, until you download its pack. This is slightly faster because of this. @@ -689,7 +689,7 @@ I like =comment-dwim= (~M-;~), and I like =comment-box=, but I have an odd perso (widen))) #+end_src And a keybinding: -#+begin_src emacs-lisp +#+begin_src emacs-lisp :tangle no (ha-local-leader :keymaps 'prog-mode-map "c" '(:ignore t :which-key "comment") "c l" '("comment line" . ha-comment-line))