|
11 | 11 | ;; URL: http://github.com/clojure-emacs/clojure-mode
|
12 | 12 | ;; Keywords: languages clojure clojurescript lisp
|
13 | 13 | ;; Version: 5.6.0-cvs
|
14 |
| -;; Package-Requires: ((emacs "24.3")) |
| 14 | +;; Package-Requires: ((emacs "24.4")) |
15 | 15 |
|
16 | 16 | ;; This file is not part of GNU Emacs.
|
17 | 17 |
|
|
68 | 68 | (require 'imenu)
|
69 | 69 | (require 'newcomment)
|
70 | 70 | (require 'align)
|
| 71 | +(require 'subr-x) |
71 | 72 |
|
72 | 73 | (declare-function lisp-fill-paragraph "lisp-mode" (&optional justify))
|
73 | 74 |
|
@@ -227,6 +228,10 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
|
227 | 228 | (define-key map (kbd "n h") #'clojure-insert-ns-form-at-point)
|
228 | 229 | (define-key map (kbd "n u") #'clojure-update-ns)
|
229 | 230 | (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) |
230 | 235 | map)
|
231 | 236 | "Keymap for Clojure refactoring commands.")
|
232 | 237 | (fset 'clojure-refactor-map clojure-refactor-map)
|
@@ -260,6 +265,11 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
|
260 | 265 | "--"
|
261 | 266 | ["Unwind once" clojure-unwind]
|
262 | 267 | ["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]) |
263 | 273 | ("Documentation"
|
264 | 274 | ["View a Clojure guide" clojure-view-guide]
|
265 | 275 | ["View a Clojure reference section" clojure-view-reference-section]
|
@@ -440,20 +450,31 @@ ENDP and DELIMITER."
|
440 | 450 |
|
441 | 451 | (declare-function paredit-open-curly "ext:paredit")
|
442 | 452 | (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)))) |
443 | 461 |
|
444 | 462 | (defun clojure-paredit-setup (&optional keymap)
|
445 | 463 | "Make \"paredit-mode\" play nice with `clojure-mode'.
|
446 | 464 |
|
447 | 465 | 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`." |
449 | 469 | (when (>= paredit-version 21)
|
450 | 470 | (let ((keymap (or keymap clojure-mode-map)))
|
451 | 471 | (define-key keymap "{" #'paredit-open-curly)
|
452 | 472 | (define-key keymap "}" #'paredit-close-curly))
|
453 | 473 | (add-to-list 'paredit-space-for-delimiter-predicates
|
454 | 474 | #'clojure-space-for-delimiter-p)
|
455 | 475 | (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))) |
457 | 478 |
|
458 | 479 | (defun clojure-mode-variables ()
|
459 | 480 | "Set up initial buffer-local variables for Clojure mode."
|
@@ -1775,6 +1796,18 @@ current sexp."
|
1775 | 1796 | :safe #'booleanp
|
1776 | 1797 | :type 'boolean)
|
1777 | 1798 |
|
| 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 | + |
1778 | 1811 | (defun clojure--maybe-unjoin-line ()
|
1779 | 1812 | "Undo a `join-line' done by a threading command."
|
1780 | 1813 | (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"
|
2061 | 2094 | (forward-sexp 2)
|
2062 | 2095 | (transpose-sexps 1)))))
|
2063 | 2096 |
|
| 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 | + |
2064 | 2305 |
|
2065 | 2306 | ;;; ClojureScript
|
2066 | 2307 | (defconst clojurescript-font-lock-keywords
|
|
0 commit comments