Skip to content

Commit 881c02a

Browse files
benedekfazekasbbatsov
authored andcommitted
Port let related refactorings from clj-refactor.el
Migrate introduce let, move to let from clj-refactor.el. Add introduce expanded let, forward slurp into let and backward slurp into let. Implementation follows the main outlines of the cljr code but is reworked at certain places. Major differences are as follows: - Expanded let is introduced: with a prefix argument let introduced N lists up with all the occurrences of bound form replaced at addition time. - New function: slurp function into let form forward and backward. Added value again is to replace bounded forms with their bound names in the slurped forms. prefix argument can be used again to slurp multiple forms into the let. - Expand let is not ported from cljr. Instead `paredit-convolute-sexp` is advised to replace forms with bound names when used on let like form. Further notes: - `string-trim` is moved upstream from cider (after merging this, cider can be refactored to use the trim fns from `clojure-mode`) Advice `paredit-convolute-sexp' when used on a let form as drop in replacement for `cljr-expand-let`. Depend on emacs 24.4 as `advice-add` is not available in 24.3 and also use `subr-x` for string trimming.
1 parent d3034dc commit 881c02a

File tree

5 files changed

+496
-7
lines changed

5 files changed

+496
-7
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ matrix:
66
allow_failures:
77
- env: EMACS=emacs-snapshot
88
before_install:
9-
# Stable Emacs 24.3
10-
- sudo add-apt-repository -y ppa:cassou/emacs
9+
# Stable Emacs 24.4
10+
- sudo add-apt-repository -y ppa:adrozdoff/emacs
1111
# Nightly Emacs snapshot builds
1212
- sudo add-apt-repository -y ppa:ubuntu-elisp/ppa
1313
# Update and install the Emacs for our environment

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* New interactive command `clojure-view-grimoire`.
1212
* New interactive command `clojure-view-style-guide`.
1313
* Make the refactoring keymap prefix customizable via `clojure-refactor-map-prefix`.
14+
* Port and rework let related features from clj-refactor. Available features: introduce-let, move to let, forward slurp form into let, backward slurp form into let.
1415

1516
## 5.5.2 (2016-08-03)
1617

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ specific `clojure-mode` release.**
3333
- [Threading macros](#threading-macros-related-features)
3434
- [Cycling things](#cycling-things)
3535
- [Convert collection](#convert-collection)
36+
- [Let expression](#let-expression)
3637
- [Related packages](#related-packages)
3738
- [REPL Interaction](#repl-interaction)
3839
- [Basic REPL](#basic-repl)
@@ -264,16 +265,49 @@ Unwind and remove the threading macro. See demonstration on the
264265

265266
* Cycle privacy
266267

267-
Cycle privacy of `def`s or `defn`s. Use metadata explicitly with setting `clojure-use-metadata-for-privacy` to `t` for `defn`s too. See demonstration on the [clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-privacy).
268+
Cycle privacy of `def`s or `defn`s. Use metadata explicitly with setting
269+
`clojure-use-metadata-for-privacy` to `t` for `defn`s too. See demonstration
270+
on the [clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-privacy).
268271

269272
* Cycle if/if-not
270273

271-
Find the closest if or if-not up the syntax tree and toggle it. Also transpose the "else" and "then" branches, keeping the semantics the same as before. See demonstration on the [clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-if).
274+
Find the closest if or if-not up the syntax tree and toggle it.
275+
Also transpose the "else" and "then" branches, keeping the semantics
276+
the same as before. See demonstration on the [clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-if).
272277

273278
### Convert collection
274279

275280
Convert any given collection at point to list, quoted list, map, vector or set.
276281

282+
### Let expression
283+
284+
* Introduce let
285+
286+
Introduce a new let form. Put the current form into its binding form with
287+
a name provided by the user as a bound name. If called with a numeric prefix
288+
put the let form Nth level up in the form hierarchy. See demonstration on the
289+
[clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-introduce-let).
290+
291+
* Move to let
292+
293+
Move the current form to the closest let's binding form. Replace
294+
all occurrences of the form in the body of the let. See demonstration on the
295+
[clj-refactor.el wiki](https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-move-to-let).
296+
297+
* Forward slurp form into let
298+
299+
Slurp the next form after the let into the let. Replace all occurrences
300+
of the bound forms in the form added to the let form. If called with
301+
a prefix argument slurp the next n forms.
302+
303+
* Backward slurp form into let
304+
305+
Slurp the form before the let into the let. Replace all occurrences
306+
of the bound forms in the form added to the let form. If called with
307+
a prefix argument slurp the previous n forms.
308+
309+
`paredit-convolute-sexp` is advised to replace occurrences of bound forms with their bound names when convolute is used on a let form.
310+
277311
## Related packages
278312

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

clojure-mode.el

Lines changed: 244 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
;; URL: http://github.com/clojure-emacs/clojure-mode
1212
;; Keywords: languages clojure clojurescript lisp
1313
;; Version: 5.6.0-cvs
14-
;; Package-Requires: ((emacs "24.3"))
14+
;; Package-Requires: ((emacs "24.4"))
1515

1616
;; This file is not part of GNU Emacs.
1717

@@ -68,6 +68,7 @@
6868
(require 'imenu)
6969
(require 'newcomment)
7070
(require 'align)
71+
(require 'subr-x)
7172

7273
(declare-function lisp-fill-paragraph "lisp-mode" (&optional justify))
7374

@@ -227,6 +228,10 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
227228
(define-key map (kbd "n h") #'clojure-insert-ns-form-at-point)
228229
(define-key map (kbd "n u") #'clojure-update-ns)
229230
(define-key map (kbd "n s") #'clojure-sort-ns)
231+
(define-key map (kbd "s i") #'clojure-introduce-let)
232+
(define-key map (kbd "s m") #'clojure-move-to-let)
233+
(define-key map (kbd "s f") #'clojure-let-forward-slurp-sexp)
234+
(define-key map (kbd "s b") #'clojure-let-backward-slurp-sexp)
230235
map)
231236
"Keymap for Clojure refactoring commands.")
232237
(fset 'clojure-refactor-map clojure-refactor-map)
@@ -260,6 +265,11 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
260265
"--"
261266
["Unwind once" clojure-unwind]
262267
["Fully unwind a threading macro" clojure-unwind-all])
268+
("Let expression"
269+
["Introduce let" clojure-introduce-let]
270+
["Move to let" clojure-move-to-let]
271+
["Forward slurp form into let" clojure-let-forward-slurp-sexp]
272+
["Backward slurp form into let" clojure-let-backward-slurp-sexp])
263273
("Documentation"
264274
["View a Clojure guide" clojure-view-guide]
265275
["View a Clojure reference section" clojure-view-reference-section]
@@ -440,20 +450,31 @@ ENDP and DELIMITER."
440450

441451
(declare-function paredit-open-curly "ext:paredit")
442452
(declare-function paredit-close-curly "ext:paredit")
453+
(declare-function paredit-convolute-sexp "ext:paredit")
454+
455+
(defun clojure--replace-let-bindings-and-indent (orig-fun &rest args)
456+
"Advise `paredit-convolute-sexp' to replace s-expressions with their bound name if a let form was convoluted."
457+
(save-excursion
458+
(backward-sexp)
459+
(when (looking-back clojure--let-regexp)
460+
(clojure--replace-sexps-with-bindings-and-indent))))
443461

444462
(defun clojure-paredit-setup (&optional keymap)
445463
"Make \"paredit-mode\" play nice with `clojure-mode'.
446464
447465
If an optional KEYMAP is passed the changes are applied to it,
448-
instead of to `clojure-mode-map'."
466+
instead of to `clojure-mode-map'.
467+
Also advice `paredit-convolute-sexp' when used on a let form as drop in
468+
replacement for `cljr-expand-let`."
449469
(when (>= paredit-version 21)
450470
(let ((keymap (or keymap clojure-mode-map)))
451471
(define-key keymap "{" #'paredit-open-curly)
452472
(define-key keymap "}" #'paredit-close-curly))
453473
(add-to-list 'paredit-space-for-delimiter-predicates
454474
#'clojure-space-for-delimiter-p)
455475
(add-to-list 'paredit-space-for-delimiter-predicates
456-
#'clojure-no-space-after-tag)))
476+
#'clojure-no-space-after-tag)
477+
(advice-add 'paredit-convolute-sexp :after #'clojure--replace-let-bindings-and-indent)))
457478

458479
(defun clojure-mode-variables ()
459480
"Set up initial buffer-local variables for Clojure mode."
@@ -1775,6 +1796,18 @@ current sexp."
17751796
:safe #'booleanp
17761797
:type 'boolean)
17771798

1799+
(defun clojure--point-after (&rest actions)
1800+
"Return POINT after performing ACTIONS.
1801+
1802+
An action is either the symbol of a function or a two element
1803+
list of (fn args) to pass to `apply''"
1804+
(save-excursion
1805+
(dolist (fn-and-args actions)
1806+
(let ((f (if (listp fn-and-args) (car fn-and-args) fn-and-args))
1807+
(args (if (listp fn-and-args) (cdr fn-and-args) nil)))
1808+
(apply f args)))
1809+
(point)))
1810+
17781811
(defun clojure--maybe-unjoin-line ()
17791812
"Undo a `join-line' done by a threading command."
17801813
(when (get-text-property (point) 'clojure-thread-line-joined)
@@ -2061,6 +2094,214 @@ See: https://github.com/clojure-emacs/clj-refactor.el/wiki/cljr-cycle-if"
20612094
(forward-sexp 2)
20622095
(transpose-sexps 1)))))
20632096

2097+
;;; let related stuff
2098+
2099+
(defvar clojure--let-regexp
2100+
"\(\\(when-let\\|if-let\\|let\\)\\(\\s-*\\|\\[\\)"
2101+
"Regexp matching let like expressions, i.e. let, when-let, if-let.
2102+
2103+
The first match-group is the let expression, the second match-group is the whitespace or the opening square bracket if no whitespace between the let expression and the bracket.")
2104+
2105+
(defun clojure--goto-let ()
2106+
"Go to the beginning of the nearest let form."
2107+
(when (in-string-p)
2108+
(while (or (not (looking-at "("))
2109+
(in-string-p))
2110+
(backward-char)))
2111+
(ignore-errors
2112+
(while (not (looking-at clojure--let-regexp))
2113+
(backward-up-list)))
2114+
(looking-at clojure--let-regexp))
2115+
2116+
(defun clojure--inside-let-binding-p ()
2117+
(ignore-errors
2118+
(save-excursion
2119+
(let ((pos (point)))
2120+
(clojure--goto-let)
2121+
(re-search-forward "\\[")
2122+
(if (< pos (point))
2123+
nil
2124+
(forward-sexp)
2125+
(up-list)
2126+
(< pos (point)))))))
2127+
2128+
(defun clojure--beginning-of-current-let-binding ()
2129+
"Move before the bound name of the current binding.
2130+
Assume that point is in the binding form of a let."
2131+
(let ((current-point (point)))
2132+
(clojure--goto-let)
2133+
(search-forward "[")
2134+
(forward-char)
2135+
(while (> current-point (point))
2136+
(forward-sexp))
2137+
(backward-sexp 2)))
2138+
2139+
(defun clojure--previous-line ()
2140+
"Keep the column position while go the previous line."
2141+
(let ((col (current-column)))
2142+
(forward-line -1)
2143+
(move-to-column col)))
2144+
2145+
(defun clojure--prepare-to-insert-new-let-binding ()
2146+
"Move to right place in the let form to insert a new binding and indent."
2147+
(if (clojure--inside-let-binding-p)
2148+
(progn
2149+
(clojure--beginning-of-current-let-binding)
2150+
(newline-and-indent)
2151+
(clojure--previous-line)
2152+
(indent-for-tab-command))
2153+
(clojure--goto-let)
2154+
(search-forward "[")
2155+
(backward-up-list)
2156+
(forward-sexp)
2157+
(down-list -1)
2158+
(backward-char)
2159+
(if (looking-at "\\[\\s-*\\]")
2160+
(forward-char)
2161+
(forward-char)
2162+
(newline-and-indent))))
2163+
2164+
(defun clojure--sexp-regexp (sexp)
2165+
(concat "\\([^[:word:]^-]\\)"
2166+
(mapconcat #'identity (mapcar 'regexp-quote (split-string sexp))
2167+
"[[:space:]\n\r]+")
2168+
"\\([^[:word:]^-]\\)"))
2169+
2170+
(defun clojure--replace-sexp-with-binding (bound-name init-expr end)
2171+
(save-excursion
2172+
(while (re-search-forward (clojure--sexp-regexp init-expr) end t)
2173+
(replace-match (concat "\\1" bound-name "\\2")))))
2174+
2175+
(defun clojure--replace-sexps-with-bindings (bindings end)
2176+
"Replace bindings with their respective bound names in the let form.
2177+
BINDINGS is the list of bound names and init expressions, END denotes the end of the let expression."
2178+
(let ((bound-name (pop bindings))
2179+
(init-expr (pop bindings)))
2180+
(when bound-name
2181+
(clojure--replace-sexp-with-binding bound-name init-expr end)
2182+
(clojure--replace-sexps-with-bindings bindings end))))
2183+
2184+
(defun clojure--replace-sexps-with-bindings-and-indent ()
2185+
(clojure--replace-sexps-with-bindings
2186+
(clojure--read-let-bindings)
2187+
(clojure--point-after 'clojure--goto-let 'forward-sexp))
2188+
(clojure-indent-region
2189+
(clojure--point-after 'clojure--goto-let)
2190+
(clojure--point-after 'clojure--goto-let 'forward-sexp)))
2191+
2192+
(defun clojure--read-let-bindings ()
2193+
"Read the bound-name and init expression pairs in the binding form.
2194+
Return a list: odd elements are bound names, even elements init expressions."
2195+
(clojure--goto-let)
2196+
(down-list 2)
2197+
(backward-char)
2198+
(let* ((start (point))
2199+
(sexp-start start)
2200+
(end (save-excursion
2201+
(forward-sexp)
2202+
(down-list -1)
2203+
(point)))
2204+
bindings)
2205+
(forward-char)
2206+
(while (/= sexp-start end)
2207+
(forward-sexp)
2208+
(let ((sexp (buffer-substring-no-properties sexp-start (point))))
2209+
(push (string-trim
2210+
(if (= start sexp-start)
2211+
(substring sexp 1)
2212+
sexp))
2213+
bindings))
2214+
(setq sexp-start (point)))
2215+
(nreverse bindings)))
2216+
2217+
(defun clojure--introduce-let-internal (name &optional n)
2218+
(if (numberp n)
2219+
(let ((init-expr-sexp (clojure-delete-and-extract-sexp)))
2220+
(insert name)
2221+
(ignore-errors (backward-up-list n))
2222+
(insert "(let" (clojure-delete-and-extract-sexp) ")")
2223+
(backward-sexp)
2224+
(down-list)
2225+
(forward-sexp)
2226+
(insert " [" name " " init-expr-sexp "]\n")
2227+
(clojure--replace-sexps-with-bindings-and-indent))
2228+
(insert "[ " (clojure-delete-and-extract-sexp) "]")
2229+
(backward-sexp)
2230+
(insert "(let " (clojure-delete-and-extract-sexp) ")")
2231+
(backward-sexp)
2232+
(down-list 2)
2233+
(insert name)
2234+
(forward-sexp)
2235+
(up-list)
2236+
(newline-and-indent)
2237+
(insert name)))
2238+
2239+
(defun clojure--move-to-let-internal (name)
2240+
(if (not (save-excursion (clojure--goto-let)))
2241+
(clojure--introduce-let-internal name)
2242+
(let ((contents (clojure-delete-and-extract-sexp)))
2243+
(insert name)
2244+
(clojure--prepare-to-insert-new-let-binding)
2245+
(insert contents)
2246+
(backward-sexp)
2247+
(insert " ")
2248+
(backward-char)
2249+
(insert name)
2250+
(clojure--replace-sexps-with-bindings-and-indent))))
2251+
2252+
(defun clojure--let-backward-slurp-sexp-internal ()
2253+
"Slurp the s-expression before the let form into the let form."
2254+
(clojure--goto-let)
2255+
(backward-sexp)
2256+
(let ((sexp (string-trim (clojure-delete-and-extract-sexp))))
2257+
(delete-blank-lines)
2258+
(down-list)
2259+
(forward-sexp 2)
2260+
(newline-and-indent)
2261+
(insert sexp)
2262+
(clojure--replace-sexps-with-bindings-and-indent)))
2263+
2264+
;;;###autoload
2265+
(defun clojure-let-backward-slurp-sexp (&optional n)
2266+
"Slurp the s-expression before the let form into the let form.
2267+
With a numberic prefix argument slurp the previous N s-expression into the let form."
2268+
(interactive "p")
2269+
(unless n (setq n 1))
2270+
(dotimes (k n)
2271+
(save-excursion (clojure--let-backward-slurp-sexp-internal))))
2272+
2273+
(defun clojure--let-forward-slurp-sexp-internal ()
2274+
"Slurp the next s-expression after the let form into the let form."
2275+
(clojure--goto-let)
2276+
(forward-sexp)
2277+
(let ((sexp (string-trim (clojure-delete-and-extract-sexp))))
2278+
(down-list -1)
2279+
(newline-and-indent)
2280+
(insert sexp)
2281+
(clojure--replace-sexps-with-bindings-and-indent)))
2282+
2283+
;;;###autoload
2284+
(defun clojure-let-forward-slurp-sexp (&optional n)
2285+
"Slurp the next s-expression after the let form into the let form.
2286+
With a numeric prefix argument slurp the next N s-expressions into the let form."
2287+
(interactive "p")
2288+
(unless n (setq n 1))
2289+
(dotimes (k n)
2290+
(save-excursion (clojure--let-forward-slurp-sexp-internal))))
2291+
2292+
;;;###autoload
2293+
(defun clojure-introduce-let (&optional n)
2294+
"Create a let form, binding the form at point.
2295+
With a numeric prefix argument the let is introduced N lists up."
2296+
(interactive "P")
2297+
(clojure--introduce-let-internal (read-from-minibuffer "Name of bound symbol: ") n))
2298+
2299+
;;;###autoload
2300+
(defun clojure-move-to-let ()
2301+
"Move the form at point to a binding in the nearest let."
2302+
(interactive)
2303+
(clojure--move-to-let-internal (read-from-minibuffer "Name of bound symbol: ")))
2304+
20642305

20652306
;;; ClojureScript
20662307
(defconst clojurescript-font-lock-keywords

0 commit comments

Comments
 (0)