Skip to content

Commit da67712

Browse files
committed
Merge pull request #379 from clojure-emacs/threading-refactor
Add threading macros related refactorings
2 parents 45bdd88 + 1077718 commit da67712

File tree

4 files changed

+690
-0
lines changed

4 files changed

+690
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
* When aligning forms with `clojure-align` (or with the automatic align feature), blank lines will divide alignment regions.
88
* [#378](https://github.com/clojure-emacs/clojure-mode/issues/378): Font-lock escape characters in strings.
9+
* Port threading macros related features from clj-refactor.el. Available refactorings: thread, unwind, thread first all, thread last all, unwind all.
910

1011
## 5.3.0 (2016-04-04)
1112

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ specific `clojure-mode` release.**
2929
- [Indentation of function forms](#indentation-of-function-forms)
3030
- [Indentation of macro forms](#indentation-of-macro-forms)
3131
- [Vertical alignment](#vertical-alignment)
32+
- [Refactoring support](#refactoring-support)
33+
- [Threading macros](#threading-macros-related-features)
3234
- [Related packages](#related-packages)
3335
- [REPL Interaction](#repl-interaction)
3436
- [Basic REPL](#basic-repl)
@@ -220,6 +222,32 @@ This can also be done automatically (as part of indentation) by
220222
turning on `clojure-align-forms-automatically`. This way it will
221223
happen whenever you select some code and hit `TAB`.
222224

225+
## Refactoring support
226+
227+
The available refactorings were originally created and maintained by the clj-refactor.el team. The ones implemented in Elisp only are gradually migrated to Clojure mode.
228+
229+
### Threading macros related features
230+
231+
* Thread an other expression.
232+
233+
Thread another form into the surrounding thread. Both `->>` and `->` variants are supported. See demonstration on the [clj-refactor.el Wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-thread).
234+
235+
* Unwind a threaded expression.
236+
237+
Supports both `->>` and `->`. See demonstration on the [clj-refactor.el Wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-unwind-thread).
238+
239+
* Wrap in thread first (`->`) and fully thread.
240+
241+
Introduce the thread first macro and rewrite the entire form. With a prefix argument do not thread the last form. See demonstration on the [clj-refactor.el Wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-thread-first-all).
242+
243+
* Wrap in thread last (`->>`) and fully thread.
244+
245+
Introduce the thread last macro and rewrite the entire form. With a prefix argument do not thread the last form. See demonstration on the [clj-refactor.el Wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-thread-last-all).
246+
247+
* Fully unwind a threaded expression.
248+
249+
Unwind and remove the threading macro. See demonstration on the [clj-refactor.el Wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-unwind-all).
250+
223251
## Related packages
224252

225253
* [clojure-mode-extra-font-locking][] provides additional font-locking

clojure-mode.el

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
195195
(let ((map (make-sparse-keymap)))
196196
(define-key map (kbd "C-:") #'clojure-toggle-keyword-string)
197197
(define-key map (kbd "C-c SPC") #'clojure-align)
198+
(define-key map (kbd "C-c C-r t") #'clojure-thread)
199+
(define-key map (kbd "C-c C-r u") #'clojure-unwind)
200+
(define-key map (kbd "C-c C-r f") #'clojure-thread-first-all)
201+
(define-key map (kbd "C-c C-r l") #'clojure-thread-last-all)
202+
(define-key map (kbd "C-c C-r a") #'clojure-unwind-all)
198203
(easy-menu-define clojure-mode-menu map "Clojure Mode Menu"
199204
'("Clojure"
200205
["Toggle between string & keyword" clojure-toggle-keyword-string]
@@ -205,6 +210,13 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
205210
"--"
206211
["Align expression" clojure-align]
207212
"--"
213+
("Refactor -> and ->>"
214+
["Fully thread a form with ->" clojure-thread-first-all]
215+
["Fully thread a form with ->>" clojure-thread-last-all]
216+
["Fully unwind a threading macro" clojure-unwind-all]
217+
["Thread once more" clojure-thread]
218+
["Unwind once" clojure-unwind])
219+
"--"
208220
["Version" clojure-mode-display-version]))
209221
map)
210222
"Keymap for Clojure mode.")
@@ -1534,6 +1546,178 @@ This will skip over sexps that don't represent objects, so that ^hints and
15341546
(backward-sexp 1))
15351547
(setq n (1- n))))))
15361548

1549+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1550+
;;
1551+
;; Refactoring support
1552+
;;
1553+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1554+
(defcustom clojure-thread-all-but-last nil
1555+
"When true `cljr-thread-first-all' and `cljr-thread-last-all' don't thread the last expression."
1556+
:package-version '(clojure-mode . "5.4.0")
1557+
:safe #'booleanp
1558+
:type 'boolean)
1559+
1560+
(defun clojure--unwind-last ()
1561+
(forward-sexp)
1562+
(save-excursion
1563+
(let ((contents (clojure-delete-and-extract-sexp)))
1564+
(when (looking-at " *\n")
1565+
(join-line -1))
1566+
(clojure--ensure-parens-around-function-names)
1567+
(let* ((sexp-beg-line (line-number-at-pos))
1568+
(sexp-end-line (progn (forward-sexp)
1569+
(line-number-at-pos)))
1570+
(multiline-sexp-p (not (= sexp-beg-line sexp-end-line))))
1571+
(down-list -1)
1572+
(when multiline-sexp-p
1573+
(newline))
1574+
(insert contents)
1575+
(when multiline-sexp-p
1576+
(clojure-indent-line)))))
1577+
(forward-char))
1578+
1579+
(defun clojure--ensure-parens-around-function-names ()
1580+
(clojure--looking-at-non-logical-sexp)
1581+
(unless (looking-at "(")
1582+
(insert-parentheses 1)
1583+
(backward-up-list)))
1584+
1585+
(defun clojure--unwind-first ()
1586+
"Unwind a thread first macro once.
1587+
Point must be between the opening paren and the -> symbol."
1588+
(forward-sexp)
1589+
(save-excursion
1590+
(let ((contents (clojure-delete-and-extract-sexp)))
1591+
(when (looking-at " *\n")
1592+
(join-line -1))
1593+
(clojure--ensure-parens-around-function-names)
1594+
(down-list)
1595+
(forward-sexp)
1596+
(insert contents)))
1597+
(forward-char))
1598+
1599+
(defun clojure--pop-out-of-threading ()
1600+
(save-excursion
1601+
(down-list 2)
1602+
(backward-up-list)
1603+
(raise-sexp)
1604+
(let ((beg (point))
1605+
(end (progn
1606+
(forward-sexp)
1607+
(point))))
1608+
(clojure-indent-region beg end))))
1609+
1610+
(defun clojure--nothing-more-to-unwind ()
1611+
(save-excursion
1612+
(let ((beg (point)))
1613+
(forward-sexp)
1614+
(down-list -1)
1615+
(backward-sexp 2) ;; the last sexp, the threading macro
1616+
(when (looking-back "(\\s-*")
1617+
(backward-up-list)) ;; and the paren
1618+
(= beg (point)))))
1619+
1620+
;;;###autoload
1621+
(defun clojure-unwind ()
1622+
"Unwind thread at point or above point by one level.
1623+
Return nil if there are no more levels to unwind."
1624+
(interactive)
1625+
(ignore-errors
1626+
(when (looking-at "(")
1627+
(forward-char 1)
1628+
(forward-sexp 1)))
1629+
(search-backward-regexp "([^-]*->")
1630+
(if (clojure--nothing-more-to-unwind)
1631+
(progn (clojure--pop-out-of-threading)
1632+
nil)
1633+
(down-list)
1634+
(cond
1635+
((looking-at "[^-]*->\\_>") (clojure--unwind-first))
1636+
((looking-at "[^-]*->>\\_>") (clojure--unwind-last)))
1637+
t))
1638+
1639+
;;;###autoload
1640+
(defun clojure-unwind-all ()
1641+
"Fully unwind thread at point or above point."
1642+
(interactive)
1643+
(while (clojure-unwind)))
1644+
1645+
(defun clojure--remove-superfluous-parens ()
1646+
(when (looking-at "([^ )]+)")
1647+
(delete-pair)))
1648+
1649+
(defun clojure--thread-first ()
1650+
(down-list)
1651+
(forward-symbol 1)
1652+
(unless (looking-at ")")
1653+
(let ((contents (clojure-delete-and-extract-sexp)))
1654+
(backward-up-list)
1655+
(just-one-space 0)
1656+
(insert contents)
1657+
(newline-and-indent)
1658+
(clojure--remove-superfluous-parens)
1659+
t)))
1660+
1661+
(defun clojure--thread-last ()
1662+
(forward-sexp 2)
1663+
(down-list -1)
1664+
(backward-sexp)
1665+
(unless (looking-back "(")
1666+
(let ((contents (clojure-delete-and-extract-sexp)))
1667+
(just-one-space 0)
1668+
(backward-up-list)
1669+
(insert contents)
1670+
(newline-and-indent)
1671+
(clojure--remove-superfluous-parens)
1672+
;; cljr #255 Fix dangling parens
1673+
(backward-up-list)
1674+
(forward-sexp)
1675+
(when (looking-back "^\\s-*)+\\s-*")
1676+
(join-line))
1677+
t)))
1678+
1679+
(defun clojure--threadable-p ()
1680+
(save-excursion
1681+
(forward-symbol 1)
1682+
(looking-at "[\n\r\t ]*(")))
1683+
1684+
;;;###autoload
1685+
(defun clojure-thread ()
1686+
"Thread by one more level an existing threading macro."
1687+
(interactive)
1688+
(ignore-errors
1689+
(when (looking-at "(")
1690+
(forward-char 1)
1691+
(forward-sexp 1)))
1692+
(search-backward-regexp "([^-]*->")
1693+
(down-list)
1694+
(when (clojure--threadable-p)
1695+
(cond
1696+
((looking-at "[^-]*->\\_>") (clojure--thread-first))
1697+
((looking-at "[^-]*->>\\_>") (clojure--thread-last)))))
1698+
1699+
(defun clojure--thread-all (first-or-last-thread but-last)
1700+
(save-excursion
1701+
(insert-parentheses 1)
1702+
(insert first-or-last-thread))
1703+
(while (save-excursion (clojure-thread)))
1704+
(when (or but-last clojure-thread-all-but-last)
1705+
(clojure-unwind)))
1706+
1707+
;;;###autoload
1708+
(defun clojure-thread-first-all (but-last)
1709+
"Fully thread the form at point using ->.
1710+
When BUT-LAST is passed the last expression is not threaded."
1711+
(interactive "P")
1712+
(clojure--thread-all "-> " but-last))
1713+
1714+
;;;###autoload
1715+
(defun clojure-thread-last-all (but-last)
1716+
"Fully thread the form at point using ->>.
1717+
When BUT-LAST is passed the last expression is not threaded."
1718+
(interactive "P")
1719+
(clojure--thread-all "->> " but-last))
1720+
15371721
(defconst clojurescript-font-lock-keywords
15381722
(eval-when-compile
15391723
`(;; ClojureScript built-ins

0 commit comments

Comments
 (0)