branch: elpa/cider
commit cb910a0dcd07e83cbb9e364e34950502f7c57a9f
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Convert modern indent specs to legacy for clojure-mode compatibility
orchard's indent inference tables now emit modern tuple-format
specs (e.g., [[:block 1] [:inner 0]]) which arrive via nREPL as
the :style/indent metadata. Convert these to legacy format before
passing to clojure-mode's clojure-get-indent-function hook, so
older clojure-mode versions that don't understand the modern format
still work correctly.
New functions:
- cider--modern-indent-spec-p: detect modern tuple format
- cider--indent-spec-to-legacy: convert modern → legacy
The conversion happens in cider--get-symbol-indent after reading
and vector-to-list conversion.
---
CHANGELOG.md | 3 +--
lisp/cider-mode.el | 12 +++++++--
lisp/cider-util.el | 64 ++++++++++++++++++++++++++++++++++++++++++++++++
test/cider-util-tests.el | 34 +++++++++++++++++++++++++
4 files changed, 109 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab36ab7473..62311f44e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,8 +13,7 @@
### Changes
- Bump the injected nREPL version to 1.6.
-### Changes
-
+- Convert modern tuple-format indent specs (e.g. `[[:block 1] [:inner 0]]`) to
legacy format for compatibility with older clojure-mode versions.
- Rename `cider-eval-spinner-type`, `cider-show-eval-spinner`, and
`cider-eval-spinner-delay` to `cider-spinner-type`, `cider-show-spinner`, and
`cider-spinner-delay`. The old names are kept as obsolete aliases.
## 1.21.0 (2026-02-07)
diff --git a/lisp/cider-mode.el b/lisp/cider-mode.el
index 1c5ce529e5..83367188a1 100644
--- a/lisp/cider-mode.el
+++ b/lisp/cider-mode.el
@@ -603,7 +603,11 @@ re-visited."
:group 'cider)
(defun cider--get-symbol-indent (symbol-name)
- "Return the indent metadata for SYMBOL-NAME in the current namespace."
+ "Return the indent metadata for SYMBOL-NAME in the current namespace.
+The return value is always in legacy format (integers, :defn,
+positional lists) for compatibility with all clojure-mode versions.
+Modern tuple-format specs from the nREPL backend are converted
+automatically."
(let* ((ns (let ((clojure-cache-ns t)) ; we force ns caching here for
performance reasons
;; silence bytecode warning of unused lexical var
(ignore clojure-cache-ns)
@@ -612,7 +616,11 @@ re-visited."
(indent (or (nrepl-dict-get meta "style/indent")
(nrepl-dict-get meta "indent"))))
(condition-case-unless-debug err
- (cider--deep-vector-to-list (read indent))
+ (let ((spec (cider--deep-vector-to-list (read indent))))
+ ;; Convert modern tuple specs to legacy format so that
+ ;; older clojure-mode versions (without modern format
+ ;; support) still work correctly.
+ (cider--indent-spec-to-legacy spec))
(error (message ":indent metadata on `%s' is unreadable!\nERROR: %s"
symbol-name (error-message-string err))
nil))
diff --git a/lisp/cider-util.el b/lisp/cider-util.el
index 3f40697213..b044fb7143 100644
--- a/lisp/cider-util.el
+++ b/lisp/cider-util.el
@@ -543,6 +543,70 @@ Any other value is just returned."
(mapcar #'cider--deep-vector-to-list x)
x))
+(defun cider--modern-indent-spec-p (spec)
+ "Return non-nil if SPEC is a modern tuple-based indent spec.
+Modern specs are lists of rules like ((:block N)) or ((:inner D))."
+ (and (listp spec)
+ spec
+ (cl-every (lambda (rule)
+ (and (listp rule)
+ (memq (car rule) '(:block :inner))))
+ spec)))
+
+(defun cider--indent-spec-to-legacy (spec)
+ "Convert a modern indent SPEC to legacy format for older clojure-mode.
+Returns SPEC unchanged if it is not in modern format.
+
+Modern format uses ((:block N)), ((:inner D)), ((:inner D I)).
+Legacy format uses integers, :defn, and positional lists.
+
+This ensures compatibility with clojure-mode versions that don't
+understand the modern format."
+ (if (not (cider--modern-indent-spec-p spec))
+ spec
+ (let ((block-n nil)
+ (inner-no-idx nil)
+ (inner-with-idx nil))
+ (dolist (rule spec)
+ (pcase rule
+ (`(:block ,n) (setq block-n n))
+ (`(:inner ,d) (push d inner-no-idx))
+ (`(:inner ,d ,i) (push (cons d i) inner-with-idx))))
+ (cond
+ ;; Simple: only (:block N)
+ ((and block-n (null inner-no-idx) (null inner-with-idx))
+ block-n)
+ ;; Simple: only (:inner 0)
+ ((and (null block-n) (null inner-with-idx)
+ (equal inner-no-idx '(0)))
+ :defn)
+ ;; Complex: build positional list
+ (t
+ (let ((result (list))
+ (wrap-defn (lambda (depth)
+ (let ((s :defn))
+ (dotimes (_ depth)
+ (setq s (list s)))
+ s))))
+ (when block-n
+ (setq result (list block-n)))
+ ;; Place indexed :inner rules at their positions
+ (dolist (ir inner-with-idx)
+ (let* ((depth (car ir))
+ (idx (cdr ir))
+ (pos (+ (if block-n 1 0) idx))
+ (wrapped (funcall wrap-defn depth)))
+ (while (<= (length result) pos)
+ (setq result (append result (list nil))))
+ (setf (nth pos result) wrapped)))
+ ;; Append non-indexed :inner rules (ascending depth)
+ (dolist (depth (sort inner-no-idx #'<))
+ (setq result (append result (list (funcall wrap-defn depth)))))
+ ;; Trailing nil for specs with indexed rules
+ (when inner-with-idx
+ (setq result (append result (list nil))))
+ result))))))
+
;;; Help mode
diff --git a/test/cider-util-tests.el b/test/cider-util-tests.el
index 18ad76e33c..78b51ab545 100644
--- a/test/cider-util-tests.el
+++ b/test/cider-util-tests.el
@@ -283,6 +283,40 @@ buffer."
(expect (cider--deep-vector-to-list '[bug]) :to-equal '(bug))
(expect (cider--deep-vector-to-list '(bug)) :to-equal '(bug))))
+(describe "cider--modern-indent-spec-p"
+ (it "recognizes modern specs"
+ (expect (cider--modern-indent-spec-p '((:block 1))) :to-be-truthy)
+ (expect (cider--modern-indent-spec-p '((:inner 0))) :to-be-truthy)
+ (expect (cider--modern-indent-spec-p '((:block 1) (:inner 2 0)))
:to-be-truthy))
+
+ (it "rejects legacy and non-spec values"
+ (expect (cider--modern-indent-spec-p 1) :not :to-be-truthy)
+ (expect (cider--modern-indent-spec-p :defn) :not :to-be-truthy)
+ (expect (cider--modern-indent-spec-p '(1 (:defn))) :not :to-be-truthy)))
+
+(describe "cider--indent-spec-to-legacy"
+ (it "converts simple modern specs"
+ (expect (cider--indent-spec-to-legacy '((:block 0))) :to-equal 0)
+ (expect (cider--indent-spec-to-legacy '((:block 1))) :to-equal 1)
+ (expect (cider--indent-spec-to-legacy '((:inner 0))) :to-equal :defn))
+
+ (it "converts complex multi-rule specs"
+ (expect (cider--indent-spec-to-legacy '((:block 1) (:inner 2 0)))
+ :to-equal '(1 ((:defn)) nil))
+ (expect (cider--indent-spec-to-legacy '((:block 2) (:inner 1)))
+ :to-equal '(2 (:defn)))
+ (expect (cider--indent-spec-to-legacy '((:block 1) (:inner 1)))
+ :to-equal '(1 (:defn)))
+ (expect (cider--indent-spec-to-legacy '((:block 1) (:inner 0)))
+ :to-equal '(1 :defn))
+ (expect (cider--indent-spec-to-legacy '((:inner 0) (:inner 1)))
+ :to-equal '(:defn (:defn))))
+
+ (it "returns legacy specs unchanged"
+ (expect (cider--indent-spec-to-legacy 1) :to-equal 1)
+ (expect (cider--indent-spec-to-legacy :defn) :to-equal :defn)
+ (expect (cider--indent-spec-to-legacy '(1 (:defn))) :to-equal '(1
(:defn)))))
+
(describe "cider-version-sans-patch"
:var (cider-version)
(it "returns the version sans the patch"