branch: elpa/cider
commit ef954ef0c5088a4ddbaebf3c8b1677ce34706772
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Let cider-macrostep expand the operator n/p jumped to
cider-macrostep-next-expandable/previous-expandable leave point at the
start of the target operator, but cider-macrostep-expand followed the
'sexp before point' convention, so expanding right there failed with
'No sexp before point to expand' - the user had to manually jump to the
end of the form first.
When point sits on a highlighted expandable operator, expand that
operator's enclosing form. The plain 'sexp before point' behavior is
unchanged everywhere else.
---
CHANGELOG.md | 1 +
lisp/cider-macrostep.el | 37 ++++++++++++++++++++++++++++++++-----
test/cider-macrostep-tests.el | 19 +++++++++++++++++++
3 files changed, 52 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 895ac6b399..6428ef4402 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### Bugs fixed
+- [#4115](https://github.com/clojure-emacs/cider/pull/4115): Fix inline macro
stepping (`cider-macrostep`): pressing `e`/`RET` right after `n`/`p` now
expands the operator you jumped to, instead of erroring with "No sexp before
point to expand".
- [#4111](https://github.com/clojure-emacs/cider/issues/4111): Fix the
macroexpansion commands refusing to expand `let`, `fn`, `loop` and `letfn`
(macros that double as special forms), restore the no-op expansion of non-macro
forms (useful for normalizing reader syntax like `::auto/kw` and `#(...)`), and
stop gating `cider-macroexpand-all` on the top-level operator (a
fully-recursive expansion can reach macros in nested sub-forms).
## 2.0.0 (2026-07-15)
diff --git a/lisp/cider-macrostep.el b/lisp/cider-macrostep.el
index 5b4340bdc2..87b5d629b2 100644
--- a/lisp/cider-macrostep.el
+++ b/lisp/cider-macrostep.el
@@ -404,6 +404,28 @@ faces from `cider-macrostep-gensym-faces'. A no-op when
disabled."
(or (seq-find (lambda (p) (< p (point))) (reverse positions))
(car (last positions)))))))
+(defun cider-macrostep--expandable-overlay-at (pos)
+ "Return the expandable-operator overlay covering POS, if any."
+ (seq-find (lambda (ov)
+ (and (overlay-buffer ov)
+ (<= (overlay-start ov) pos)
+ (< pos (overlay-end ov))))
+ cider-macrostep--expandable-overlays))
+
+(defun cider-macrostep--expand-bounds ()
+ "Return the (BEG . END) bounds of the form to expand, or nil.
+When point sits on a highlighted expandable operator - where `n'/`p' leave
+it - expand that operator's enclosing form. Otherwise fall back to the sexp
+before point, the `\\[cider-eval-last-sexp]' convention that
+`cider-macrostep-expand' documents."
+ (or (when-let* ((ov (cider-macrostep--expandable-overlay-at (point))))
+ (save-excursion
+ (goto-char (overlay-start ov))
+ (ignore-errors
+ (backward-up-list)
+ (cons (point) (progn (forward-sexp) (point))))))
+ (cider-macrostep--form-bounds)))
+
(defvar cider-macrostep-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "e") #'cider-macrostep-expand)
@@ -473,13 +495,16 @@ Enter `cider-macrostep-mode' when it isn't already
active."
;;;###autoload
(defun cider-macrostep-expand ()
"Expand the macro form before point one step, inline.
-Place point right after the form, as with `\\[cider-eval-last-sexp]'.
+Place point right after the form, as with `\\[cider-eval-last-sexp]'. When
+point is on a highlighted expandable operator - where `n'/`p' land it - that
+operator's form is expanded, so you can step with `n' then expand without
+repositioning.
Start a `cider-macrostep-mode' session when one isn't active; further
expansions and collapses then use that mode's key bindings."
(interactive)
(cider-ensure-session)
- (pcase-let ((`(,beg . ,end) (or (cider-macrostep--form-bounds)
+ (pcase-let ((`(,beg . ,end) (or (cider-macrostep--expand-bounds)
(user-error "No sexp before point to
expand"))))
(let ((operator (cider-macrostep--operator beg)))
(cider-ensure-macro operator)
@@ -499,11 +524,13 @@ every level. Place point right after the form, as with
`\\[cider-eval-last-sexp]'.
Like `cider-macrostep-expand', this starts a `cider-macrostep-mode' session
-when one isn't active. It does not require the form's head to be a macro,
-since a fully-recursive expansion can reach macros in nested sub-forms."
+when one isn't active, and expands the form of the expandable operator at
+point when `n'/`p' has landed there. It does not require the form's head to
+be a macro, since a fully-recursive expansion can reach macros in nested
+sub-forms."
(interactive)
(cider-ensure-session)
- (pcase-let ((`(,beg . ,end) (or (cider-macrostep--form-bounds)
+ (pcase-let ((`(,beg . ,end) (or (cider-macrostep--expand-bounds)
(user-error "No sexp before point to
expand"))))
(let* ((code (buffer-substring-no-properties beg end))
(expansion (cider-macrostep--expand code "macroexpand-all")))
diff --git a/test/cider-macrostep-tests.el b/test/cider-macrostep-tests.el
index 516841cc6b..c81a925971 100644
--- a/test/cider-macrostep-tests.el
+++ b/test/cider-macrostep-tests.el
@@ -176,6 +176,25 @@
(setq cider-macrostep--expandable-overlays nil)
(expect (cider-macrostep-next-expandable) :to-throw 'user-error))))
+(describe "cider-macrostep--expand-bounds"
+ (it "expands the enclosing form when point is on an expandable operator"
+ ;; This is where `n'/`p' land point (the start of the operator). Before,
+ ;; `e'/`RET' there errored with \"No sexp before point to expand\".
+ (with-temp-buffer
+ (clojure-mode)
+ (insert "(aaa (bbb ccc))")
+ (setq cider-macrostep--expandable-overlays
+ (list (make-overlay 7 10))) ; `bbb' operator
+ (goto-char 7) ; point at the start of `bbb'
+ (expect (cider-macrostep--expand-bounds) :to-equal '(6 . 15))))
+ (it "falls back to the sexp before point elsewhere"
+ (with-temp-buffer
+ (clojure-mode)
+ (insert "(aaa (bbb))")
+ (setq cider-macrostep--expandable-overlays nil)
+ (goto-char (point-max)) ; after the whole form
+ (expect (cider-macrostep--expand-bounds) :to-equal '(1 . 12)))))
+
(describe "cider-macrostep gensym coloring"
(it "matches gensyms but not ordinary symbols"
(with-temp-buffer