Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 10 additions & 4 deletions doc/modules/ROOT/pages/config/indentation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,17 @@ To get `clojure-mode` to indent it properly you'll need to add the following cod

[source,lisp]
----
(put-clojure-indent 'with-in-str 1)
(put-clojure-indent 'with-in-str '((:block 1)))

;; or

(define-clojure-indent
(with-in-str 1)
(with-in-str '((:block 1))))
----

NOTE: The legacy shorthand `(put-clojure-indent 'with-in-str 1)` is also
accepted for backward compatibility but will be removed in clojure-mode 6.

TIP: You can find more details https://github.com/clojure-emacs/clojure-mode#indentation-of-macro-forms[here].

== Dynamic Indentation
Expand Down Expand Up @@ -128,13 +131,13 @@ they've written (using an example in core):
(baz))
----

And here's a more complex one:
And here's a more complex one using the modern tuple format:

[source,clojure]
----
(defmacro letfn
"[DOCSTRING]"
{:style/indent [1 [[:defn]] :form]}
{:style/indent [[:block 1] [:inner 2 0]]}
[fnspecs & body]
...cut for brevity...)

Expand All @@ -152,6 +155,9 @@ be either just a number, or one of the keywords `:defn` or `:form`. A full
description of the spec is provided in the
xref:indent_spec.adoc[indent spec section of the manual].

NOTE: The legacy positional format (e.g., `[1 [[:defn]] :form]`) is also
accepted but will be removed in clojure-mode 6.

If you _don't_ want to use this feature, you can disable it by setting
`cider-dynamic-indentation` to `nil` in your Emacs init file.

Expand Down
237 changes: 194 additions & 43 deletions doc/modules/ROOT/pages/indent_spec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,88 @@ under the `:style/indent` key.
...cut for brevity...)
----

It can take one of 3 forms:
There are two supported formats for indent specs: the **modern tuple format**
(preferred) and the **legacy positional format** (deprecated, will be removed in
clojure-mode 6).

=== Modern format (preferred)

The modern format uses explicit rule tuples and is shared across `clojure-mode`,
`clojure-ts-mode`, and https://github.com/weavejester/cljfmt[cljfmt]. The
format is identical in all three tools, so indent specs are portable across
editors and formatters.

==== Key concepts

Before diving into the rules, here are the terms used throughout:

**Body-style indentation**:: Arguments are indented by 2 spaces relative to the
enclosing form. This is the standard indentation for macro bodies:
+
[source,clojure]
----
(when true
(foo) ;; body — indented 2 spaces
(bar))
----

**Special arguments**:: Arguments that precede the body. When placed on their
own line, they get additional indentation to visually distinguish them from
the body:
+
[source,clojure]
----
(defrecord TheNameOfTheRecord
[a pretty long argument list] ;; special arg — extra indentation
SomeType ;; body — standard 2-space indentation
(method [this] ...))
----

**Depth**:: The nesting level within the form, counting from 0. Depth 0 is the
form's direct arguments, depth 1 is the arguments _inside_ those arguments,
depth 2 is one level deeper, and so on.

**Position**:: The 0-indexed argument position within the enclosing form
(excluding the form name itself). For example, in `(let [x 1] body)`, the
binding vector `[x 1]` is at position 0 and `body` is at position 1.

==== Rule types

Each rule is a vector of the form `[:block N]`, `[:inner D]`, or `[:inner D I]`:

[cols="1,3"]
|===
| Rule | Meaning

| `[:block N]`
| The first N arguments are "special" (indented further when on their own line);
remaining arguments get body-style indentation (2 spaces).

| `[:inner D]`
| At nesting depth D inside the form, all sub-forms get body-style indentation.
`[:inner 0]` means direct arguments are body-indented (like `defn`).
`[:inner 1]` means forms _inside_ the arguments are body-indented (like method
bodies in `defprotocol`).

| `[:inner D I]`
| Like `[:inner D]`, but restricted to position I within the enclosing form.
Used when only some arguments at a given depth need body-style indentation.
|===

Rules are combined in a vector. For example:

* `1` or `[[:block 1]]` — one special arg, then body (e.g., `when`, `let`)
* `:defn` or `[[:inner 0]]` — all args are body (e.g., `defn`, `fn`)
* `[[:block 2] [:inner 1]]` — two special args, nested sub-forms get body indent (e.g., `defrecord`, `deftype`)
* `[[:block 1] [:inner 2 0]]` — one special arg, depth-2 nesting at position 0 only (e.g., `letfn`)

Simple specs (an integer or `:defn`) are shorthand for the corresponding
single-rule vector.

=== Legacy format (deprecated)

The legacy format uses positional lists where each element controls indentation
at the corresponding argument position. It takes one of these forms:

* Absent, meaning _"indent like a regular function call"_.
* An integer or a keyword `x`, which is shorthand for the list `[x]`.
Expand All @@ -33,6 +114,9 @@ internally (if it's not a form the spec is irrelevant).
** If the function/macro has more arguments than the list has elements, the last
element of the list applies to all remaining arguments.

NOTE: The legacy positional format will be removed in clojure-mode 6. New code
should use the modern tuple format.

'''

== Examples
Expand All @@ -42,77 +126,125 @@ Here we go into several examples using some well-known macros and forms from
don't need to specify them. They are just examples to guide you when writing
indent specs for your own macros, or for macros from third party libs.

One very simple example is the `do` form. All of its arguments get the same
indentation, and none of them are special. So its indent spec is simply `[0]`,
or `0` for short.
=== Simple specs

The `do` form has no special arguments — all args get body indentation.
Its indent spec is simply `0` (shorthand for `[[:block 0]]`).

[source,clojure]
----
(do
(something)
(something) ;; body (2-space indent)
(quick))
----

The `when-let` macro has one special argument (the binding vector).
Its indent spec is `1` (shorthand for `[[:block 1]]`).

(do (whatever)
(you)
(want))
[source,clojure]
----
(when-let [x (foo)] ;; position 0 — special arg
(bar x)) ;; position 1+ — body
----

Sticking to simplicity, the `when-let*` macro has one special argument (the
binding vector) and there's no out-of-the-ordinary internal structure
involved. So the indent spec is just `1` (which is shorthand for `[1]`).
The `defn` macro uses `:defn` (shorthand for `[[:inner 0]]`), meaning all
arguments get body-style indentation regardless of position.

'''
[source,clojure]
----
(defn my-fn
[x] ;; depth 0 — body-indented
(inc x)) ;; depth 0 — body-indented
----

Let's see something more sophisticated. If the `defrecord` indent spec used by
`clojure-mode` is `[2 :form :form [1]]`. This is saying:
=== Multi-rule specs

* `defrecord` has 2 special arguments (the name and the arglist).
* The first two arguments have no special internal structure.
* All remaining arguments have an internal indent spec of `[1]` (which means
only the arglist is indented specially and the rest is the body).
`defrecord` uses `[[:block 2] [:inner 1]]`. This means:

* `[:block 2]` — 2 special arguments (the name and the fields vector).
* `[:inner 1]` — at depth 1 (inside the protocol method forms), use body-style
indentation. This is what makes method bodies indent correctly.

[source,clojure]
----
(defrecord Thing [a]
FileNameMap
(getContentTypeFor [_ file-name]
(defrecord Thing [a] ;; depth 0, pos 0-1: special args ([:block 2])
FileNameMap ;; depth 0, pos 2+: body
(getContentTypeFor [_ file-name] ;; depth 1: body-indented ([:inner 1])
(str a "-" file-name))
Object
(toString [_]
"My very own thing!!"))
----

For something even more complicated: `letfn` is `+[1 [[:defn]] :form]+`. This means
`letfn` uses `[[:block 1] [:inner 2 0]]`. This is the most complex built-in
spec:

* `letfn` has one special argument (the bindings list).
* The first arg has an indent spec of `+[[:defn]]+`, which means all forms
_inside_ the first arg have an indent spec of `+[:defn]+`.
* The second argument, and all other arguments, are regular forms.
* `[:block 1]` — 1 special argument (the bindings vector).
* `[:inner 2 0]` — at depth 2, *only at position 0*, use body-style
indentation. Why position 0? Because inside the bindings vector (depth 1),
each binding is a list like `(twice [x] body)`. Position 0 in each binding
is the function name+arglist, and the body follows. The position restriction
ensures that only the function definitions inside the bindings get body-style
indentation.

[source,clojure]
----
(letfn [(twice [x]
(* x 2))
(letfn [(twice [x] ;; depth 0, pos 0: special arg ([:block 1])
(* x 2)) ;; depth 2, pos 0: body-indented ([:inner 2 0])
(six-times [y]
(* (twice y) 3))]
(six-times 15))
(six-times 15)) ;; depth 0, pos 1+: body
----

==== Choosing the right rule

As a rule of thumb:

* **Use `[:block N]`** for macros with N special arguments before a body — this
is the most common case. Examples: `when` (1), `let` (1), `condp` (2),
`catch` (2).
* **Use `[:inner 0]`** for def-like forms where all arguments are body. Examples:
`defn`, `fn`, `deftest`, `defmethod`.
* **Use `[:inner 1]`** when the form contains sub-forms whose bodies need
body-style indentation (like protocol method definitions). Examples:
`defprotocol`, `deftype`, `defrecord`.
* **Combine rules** when a form has both special args _and_ nested structure.
Examples: `defrecord` = `[:block 2]` + `[:inner 1]`.

=== Legacy format equivalents

For reference, the same specs in the legacy positional format:

[cols="1,1,1"]
|===
| Form | Modern | Legacy

| `do` | `0` or `[[:block 0]]` | `0`
| `when-let` | `1` or `[[:block 1]]` | `1`
| `defn` | `:defn` or `[[:inner 0]]` | `:defn`
| `defrecord` | `[[:block 2] [:inner 1]]` | `[2 nil nil [:defn]]`
| `defprotocol` | `[[:block 1] [:inner 1]]` | `[1 [:defn]]`
| `extend-protocol` | `[[:block 1] [:inner 0]]` | `[1 :defn]`
| `reify` | `[[:inner 0] [:inner 1]]` | `[:defn [:defn]]`
| `letfn` | `[[:block 1] [:inner 2 0]]` | `[1 [[:defn]] :form]`
|===

== Special Arguments

Many macros have a number of "special" arguments, followed by an arbitrary
number of "non-special" arguments (sometimes called the body). The "non-special"
arguments have a small indentation (usually 2 spaces). The special arguments
arguments have body-style indentation (2 spaces). The special arguments
are usually on the same line as the macro name, but, when necessary, they are
placed on a separate line with additional indentation.

For instance, `defrecord` has two special arguments, and here's how it might be indented:
This is controlled by the `[:block N]` rule. For instance, `defrecord` has
`[:block 2]` (two special arguments), and here's how it might be indented:

[source,clojure]
----
(defrecord TheNameOfTheRecord
[a pretty long argument list]
SomeType
[a pretty long argument list] ;; special arg — extra indent
SomeType ;; body — 2 spaces
(assoc [_ x]
(.assoc pretty x 10)))
----
Expand All @@ -130,11 +262,11 @@ Here's another way one could do it:

_The point of the indent spec is *not* to specify how many spaces to use._

The point is just to say "a defrecord has *2* special arguments", and then let
the editor and the user come to an agreement on how many spaces they like to use
for special and non-special arguments.
The point is just to say "a defrecord has *2* special arguments" (via
`[:block 2]`), and then let the editor and the user come to an agreement on how
many spaces they like to use for special and non-special arguments.

== Internal indentation
== Internal Indentation (Depth)

The issue goes a bit deeper. Note the last argument in that `defrecord`. A
regular function form would be internally indented as:
Expand All @@ -144,18 +276,37 @@ regular function form would be internally indented as:
(.assoc pretty x 10))
----

But this is not a regular function call, it's a definition. So we want to
specify that this form internally has 1 special argument (the arglist vector),
so that it will be indented like this:
But this is not a regular function call, it's a method definition. So we want
the method body to get body-style indentation:

----
(assoc [_ x]
(.assoc pretty x 10))
----

The indent spec does this as well. It lets you specify that, for each argument
beyond the 2nd, if it is a form, it should be internally indented as having 1
special argument.
This is what the `[:inner 1]` rule does — it says "at depth 1 (inside the
arguments of this form), use body-style indentation." Combined with
`[:block 2]`, the full spec `[[:block 2] [:inner 1]]` gives defrecord both
special argument handling and correct method body indentation.

== Namespace-Qualified Symbols

In `clojure-mode`, you can specify different indentation for the same symbol
depending on its namespace. This is useful when a library redefines a form
with different semantics:

[source,lisp]
----
;; Default indentation for `do`
(put-clojure-indent 'do '((:block 0)))

;; Custom indentation for `my-ns/do`
(put-clojure-indent 'my-ns/do '((:block 1)))
----

When a namespace-qualified symbol has no explicit spec, `clojure-mode`
automatically falls back to the unqualified symbol's spec. For example,
`clojure.core/let` uses the same spec as `let`.

== Indentation inference

Expand Down
12 changes: 10 additions & 2 deletions lisp/cider-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
Expand Down
Loading
Loading