Skip to content

Commit f759352

Browse files
committed
Add cider-macrostep-expand-all for one-shot full expansion
`cider-macrostep-expand-all' (bound to `a' in `cider-macrostep-mode') fully expands the form before point in one step via `macroexpand-all', for when you don't want to step down level by level. Unlike the stepwise command it doesn't require the head to be a macro, since a recursive expansion can still reach macros in nested sub-forms. Generalizes the internal expander helper to take the expander name.
1 parent 548d10e commit f759352

3 files changed

Lines changed: 63 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- [#3970](https://github.com/clojure-emacs/cider/pull/3970): Add `cider-macrostep-expand` for inline, in-place macro expansion with step-in/collapse (`cider-macrostep-mode`).
1313
- [#3976](https://github.com/clojure-emacs/cider/pull/3976): Underline the further-expandable sub-forms while inline macro stepping and move between them with `n`/`p` in `cider-macrostep-mode` (`cider-macrostep-highlight-expandable`).
1414
- [#3977](https://github.com/clojure-emacs/cider/pull/3977): Colorize the gensyms introduced by an inline macro expansion, each distinct gensym in its own color, so an introduced binding can be tracked (`cider-macrostep-color-gensyms`).
15+
- [#3978](https://github.com/clojure-emacs/cider/pull/3978): Add `cider-macrostep-expand-all` (`a` in `cider-macrostep-mode`) to fully expand the form at point inline in one step, instead of stepping level by level.
1516

1617
### Bugs fixed
1718

lisp/cider-macrostep.el

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@ The sentinel `none' means there was no buffer-local value to restore.")
135135
(concat
136136
(propertize " CIDER Macrostep " 'face 'mode-line-emphasis)
137137
(format " %d expansion%s " n (if (= n 1) "" "s"))
138-
(propertize "[e]xpand [c]ollapse [n]ext [p]rev [q]uit" 'face 'shadow))))
138+
(propertize "[e]xpand [a]ll [c]ollapse [n]ext [p]rev [q]uit" 'face 'shadow))))
139139

140-
(defun cider-macrostep--expand-1 (code)
141-
"Return the one-step macroexpansion of CODE in the current namespace."
140+
(defun cider-macrostep--expand (code expander)
141+
"Return the macroexpansion of CODE in the current namespace.
142+
EXPANDER is a `cider/macroexpand' expander name, such as \"macroexpand-1\"
143+
\(one level) or \"macroexpand-all\" (fully, recursively)."
142144
(cider-ensure-op-supported "cider/macroexpand")
143145
(let ((result (thread-first `("op" "cider/macroexpand"
144-
"expander" "macroexpand-1"
146+
"expander" ,expander
145147
"code" ,code
146148
"ns" ,(cider-current-ns)
147149
"display-namespaces" ,(symbol-name cider-macrostep-display-namespaces))
@@ -388,6 +390,7 @@ colors from `cider-macrostep-gensym-colors'. A no-op when disabled."
388390
(define-key map (kbd "e") #'cider-macrostep-expand)
389391
(define-key map (kbd "=") #'cider-macrostep-expand)
390392
(define-key map (kbd "RET") #'cider-macrostep-expand)
393+
(define-key map (kbd "a") #'cider-macrostep-expand-all)
391394
(define-key map (kbd "c") #'cider-macrostep-collapse)
392395
(define-key map (kbd "u") #'cider-macrostep-collapse)
393396
(define-key map (kbd "DEL") #'cider-macrostep-collapse)
@@ -461,13 +464,40 @@ expansions and collapses then use that mode's key bindings."
461464
(user-error "No sexp before point to expand"))))
462465
(let ((operator (cider-macrostep--operator beg)))
463466
(cider-macrostep--ensure-macro operator)
464-
(let ((expansion (cider-macrostep--expand-1
465-
(buffer-substring-no-properties beg end))))
467+
(let ((expansion (cider-macrostep--expand
468+
(buffer-substring-no-properties beg end) "macroexpand-1")))
466469
(unless (and (stringp expansion) (not (string-blank-p expansion)))
467470
(user-error "No expansion returned for `%s'" operator))
468471
(cider-macrostep--expand-region beg end expansion)
469472
(cider-macrostep--refresh-overlays)))))
470473

474+
;;;###autoload
475+
(defun cider-macrostep-expand-all ()
476+
"Fully expand the form before point, inline.
477+
Unlike `cider-macrostep-expand', which expands one level at a time, this
478+
expands the form all the way (`macroexpand-all'), so you needn't step through
479+
every level. Place point right after the form, as with
480+
`\\[cider-eval-last-sexp]'.
481+
482+
Like `cider-macrostep-expand', this starts a `cider-macrostep-mode' session
483+
when one isn't active. It does not require the form's head to be a macro,
484+
since a fully-recursive expansion can reach macros in nested sub-forms."
485+
(interactive)
486+
(cider-ensure-connected)
487+
(pcase-let ((`(,beg . ,end) (or (cider-macrostep--form-bounds)
488+
(user-error "No sexp before point to expand"))))
489+
(let* ((code (buffer-substring-no-properties beg end))
490+
(expansion (cider-macrostep--expand code "macroexpand-all")))
491+
(unless (and (stringp expansion) (not (string-blank-p expansion)))
492+
(user-error "No expansion returned"))
493+
;; Compare with whitespace normalized, so the printer merely reindenting
494+
;; an already-fully-expanded form still counts as "nothing to expand".
495+
(when (string= (replace-regexp-in-string "[ \t\n]+" " " (string-trim expansion))
496+
(replace-regexp-in-string "[ \t\n]+" " " (string-trim code)))
497+
(user-error "Nothing to expand"))
498+
(cider-macrostep--expand-region beg end expansion)
499+
(cider-macrostep--refresh-overlays))))
500+
471501
(defun cider-macrostep-collapse ()
472502
"Collapse the innermost expansion at point."
473503
(interactive)

test/cider-macrostep-tests.el

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,32 @@
241241
(cider-macrostep--refresh-gensyms))
242242
(expect cider-macrostep--gensym-overlays :to-be nil))))
243243

244+
(describe "cider-macrostep-expand-all"
245+
(before-each
246+
(spy-on 'cider-ensure-connected)
247+
(spy-on 'cider-macrostep--refresh-overlays))
248+
249+
(it "fully expands the form before point inline via macroexpand-all"
250+
(with-temp-buffer
251+
(clojure-mode)
252+
(insert "(when x a)")
253+
(goto-char (point-max))
254+
(spy-on 'cider-macrostep--expand :and-return-value "(if x (do a))")
255+
(cider-macrostep-expand-all)
256+
(expect (string-search "(if x" (buffer-string)) :not :to-be nil)
257+
(expect (length cider-macrostep--overlays) :to-equal 1)
258+
(expect 'cider-macrostep--expand
259+
:to-have-been-called-with "(when x a)" "macroexpand-all")))
260+
261+
(it "errors when the form has nothing to expand"
262+
(with-temp-buffer
263+
(clojure-mode)
264+
(insert "(+ 1 2)")
265+
(goto-char (point-max))
266+
(spy-on 'cider-macrostep--expand :and-return-value "(+ 1 2)")
267+
(expect (cider-macrostep-expand-all) :to-throw 'user-error)
268+
(expect cider-macrostep--overlays :to-be nil))))
269+
244270
(provide 'cider-macrostep-tests)
245271

246272
;;; cider-macrostep-tests.el ends here

0 commit comments

Comments
 (0)