Endless Parentheses

Concise ramblings on Emacs productivity.

Support on Gratipay
profile for Malabarba on Stack Exchange

Commands to thread and unwind code in Emacs-Lisp

As you may remember, one of the commands I like the most from the clj-refactor package are the ones that thread and unwind Clojure code forms for you. Now that Emacs is also getting built-in threading macros, I figured the best way to give them a fair chance in life is to also make them pretty convenient to use.

The point here is that you can place point before a paren, invoke a command, and a regular code form gets transformed into a threading macro, or vice-versa. See the linked post for what that means. Instead of writing whole new commands for that, I had a fun time just hacking clj-refactor commands to work on thread-first/last.

It should go without saying, you need clj-refactor installed for this to work.

(define-key emacs-lisp-mode-map "\C-ctf"
#'endless/elisp-thread-first)
(define-key emacs-lisp-mode-map "\C-ctl"
#'endless/elisp-thread-last)
(define-key emacs-lisp-mode-map "\C-ctu"
#'endless/elisp-unwind)
(define-key emacs-lisp-mode-map "\C-cta"
#'endless/elisp-unwind-all)

(defun endless/elisp-thread-last ()
"Turn the form at point into a `thread-last' form."
(interactive)
(cljr-thread-last-all nil)
(save-excursion
(when (search-backward "->>" nil 'noerror)
(replace-match "thread-last"))))

(defun endless/elisp-thread-first ()
"Turn the form at point into a `thread-first' form."
(interactive)
(cljr-thread-first-all nil)
(save-excursion
(when (search-backward "->" nil 'noerror)
(replace-match "thread-first"))))

(defun endless/elisp-unwind ()
"Unwind thread at point or above point by one level.
Return nil if there are no more levels to unwind."

(interactive)
(let ((p (point)))
;; Find a thread above.
(when (save-excursion
(forward-sexp 1)
(and (search-backward-regexp "\\_<thread-\\(first\\|last\\)\\_>" nil 'noerror)
;; Ensure that it contains the original point.
(save-match-data (forward-char -1)
(forward-sexp 1)
(> (point) p))))
(replace-match (if (string= (match-string 1) "first")
"->" "->>"))
(let ((thread-beginnig (match-beginning 0)))
(prog1 (cljr-unwind)
(save-excursion
(goto-char thread-beginnig)
(when (looking-at "\\_<->>?\\_>")
(replace-match (if (string= (match-string 0) "->")
"thread-first" "thread-last")))))))))

(defun endless/elisp-unwind-all ()
"Fully unwind thread at point or above point."
(interactive)
(while (endless/elisp-unwind)))

Maybe just writing it from scratch would have made for shorter code (it would certainly be more robust). But such is life. I try not to dwell too much on quick hacks.

Support on Gratipay
comments powered by Disqus