Skip to content

Commit d6a9568

Browse files
authored
Merge pull request #702 from clojure-emacs/new-indent-spec-format
Adopt modern indent spec format from clojure-ts-mode
2 parents dd25ae7 + 53d11c8 commit d6a9568

File tree

3 files changed

+428
-133
lines changed

3 files changed

+428
-133
lines changed

README.md

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,21 @@ Similarly we have the `clojure-indent-keyword-style`, which works in the followi
196196
#### Indentation of macro forms
197197

198198
The indentation of special forms and macros with bodies is controlled via
199-
`put-clojure-indent`, `define-clojure-indent` and `clojure-backtracking-indent`.
199+
`put-clojure-indent` and `define-clojure-indent`.
200200
Nearly all special forms and built-in macros with bodies have special indentation
201201
settings in `clojure-mode`. You can add/alter the indentation settings in your
202-
personal config. Let's assume you want to indent `->>` and `->` like this:
202+
personal config.
203+
204+
Indent specs use a **modern tuple format** shared with `clojure-ts-mode`:
205+
206+
| Spec | Meaning |
207+
|------|---------|
208+
| `'((:block N))` | First N args are special, rest indented as body |
209+
| `'((:inner 0))` | All args indented as body (like `defn`) |
210+
| `'((:inner D))` | Body-style indent at nesting depth D |
211+
| `'((:inner D I))` | Body-style at depth D, only at position I |
212+
213+
You can combine multiple rules. For example, let's say you want to indent `->>` and `->` like this:
203214

204215
```clojure
205216
(->> something
@@ -211,8 +222,8 @@ personal config. Let's assume you want to indent `->>` and `->` like this:
211222
You can do so by putting the following in your config:
212223

213224
```el
214-
(put-clojure-indent '-> 1)
215-
(put-clojure-indent '->> 1)
225+
(put-clojure-indent '-> '((:block 1)))
226+
(put-clojure-indent '->> '((:block 1)))
216227
```
217228

218229
This means that the body of the `->/->>` is after the first argument.
@@ -221,24 +232,28 @@ A more compact way to do the same thing is:
221232

222233
```el
223234
(define-clojure-indent
224-
(-> 1)
225-
(->> 1))
235+
(-> '((:block 1)))
236+
(->> '((:block 1))))
226237
```
227238

228239
To indent something like a definition (`defn`) you can do something like:
229240

230241
``` el
231-
(put-clojure-indent '>defn :defn)
242+
(put-clojure-indent '>defn '((:inner 0)))
232243
```
233244

234245
You can also specify different indentation settings for symbols
235246
prefixed with some ns (or ns alias):
236247

237248
```el
238-
(put-clojure-indent 'do 0)
239-
(put-clojure-indent 'my-ns/do 1)
249+
(put-clojure-indent 'do '((:block 0)))
250+
(put-clojure-indent 'my-ns/do '((:block 1)))
240251
```
241252

253+
**Note:** A legacy format using integers (e.g., `1`), keywords (`:defn`), and
254+
positional lists (e.g., `'(1 ((:defn)) nil)`) is also accepted for backward
255+
compatibility. It will be removed in clojure-mode 6.
256+
242257
##### Backtracking (contextual) indentation
243258

244259
Certain macros and special forms (e.g. `letfn`, `deftype`,
@@ -248,65 +263,54 @@ indentation**: when indenting a line, it walks up the sexp tree to
248263
find a parent form with an indent spec, then uses the current
249264
position within that spec to decide how to indent.
250265

251-
A backtracking indent spec is a **quoted list** where each element
252-
controls indentation at the corresponding argument position
253-
(0-indexed). The allowed elements are:
254-
255-
| Element | Meaning |
256-
|---------|---------|
257-
| An integer N | First N args are "special" (indented further); rest are body |
258-
| `:defn` | Indent like a function/macro body |
259-
| `:form` | Indent like a regular form |
260-
| `nil` | Use default indentation rules |
261-
| A list `(SPEC)` | This position holds a **list of forms**, each indented according to SPEC |
262-
263-
For example, `letfn` uses `'(1 ((:defn)) nil)`:
266+
Multi-rule specs combine `:block` and `:inner` rules to control
267+
nested indentation. For example, `letfn` uses `'((:block 1) (:inner 2 0))`:
264268

265269
```clojure
266-
(letfn [(twice [x] ;; pos 0spec 1 (1 special arg = the binding vector)
267-
(* x 2)) ;; inside binding → spec ((:defn)) applies:
268-
(thrice [x] ;; each binding is a list of :defn-style forms
269-
(* x 3))] ;; so function bodies get :defn indentation
270-
(+ (twice 5) ;; pos 1+ → spec nil (default → body indentation)
270+
(letfn [(twice [x] ;; (:block 1)1 special arg (the binding vector)
271+
(* x 2)) ;; (:inner 2 0) → at depth 2, position 0 in the binding
272+
(thrice [x] ;; vector, use body-style indentation
273+
(* x 3))]
274+
(+ (twice 5) ;; after the block arg → body indentation
271275
(thrice 5)))
272276
```
273277

274-
And `defrecord` uses `'(2 nil nil (:defn))`:
278+
And `defrecord` uses `'((:block 2) (:inner 1))`:
275279

276280
```clojure
277-
(defrecord MyRecord ;; pos 0spec 2 (2 special args: name + fields)
278-
[field1 field2] ;; pos 1 → spec nil (within special args zone)
279-
SomeProtocol ;; pos 2spec nil
280-
(some-method [this] ;; pos 3+ → spec (:defn) — each method gets :defn-style
281+
(defrecord MyRecord ;; (:block 2)2 special args (name + fields)
282+
[field1 field2]
283+
SomeProtocol ;; (:inner 1)nested sub-forms at depth 1
284+
(some-method [this] ;; get body-style indentation
281285
(do-stuff this)))
282286
```
283287

284-
Here are the built-in backtracking specs:
288+
Here are the built-in multi-rule specs:
285289

286290
```el
287291
(define-clojure-indent
288-
(letfn '(1 ((:defn)) nil))
289-
(deftype '(2 nil nil (:defn)))
290-
(defrecord '(2 nil nil (:defn)))
291-
(defprotocol '(1 (:defn)))
292-
(definterface '(1 (:defn)))
293-
(reify '(:defn (1)))
294-
(proxy '(2 nil nil (:defn)))
295-
(extend-protocol '(1 :defn))
296-
(extend-type '(1 :defn))
297-
(specify '(1 :defn))
298-
(specify! '(1 :defn)))
292+
(letfn '((:block 1) (:inner 2 0)))
293+
(deftype '((:block 2) (:inner 1)))
294+
(defrecord '((:block 2) (:inner 1)))
295+
(defprotocol '((:block 1) (:inner 1)))
296+
(definterface '((:block 1) (:inner 1)))
297+
(reify '((:inner 0) (:inner 1)))
298+
(proxy '((:block 2) (:inner 1)))
299+
(extend-protocol '((:block 1) (:inner 0)))
300+
(extend-type '((:block 1) (:inner 0)))
301+
(specify '((:block 1) (:inner 0)))
302+
(specify! '((:block 1) (:inner 0))))
299303
```
300304

301-
These follow the same rules as the `:style/indent` metadata specified by [cider-nrepl][].
305+
This format is shared with `clojure-ts-mode`. It also follows the same
306+
rules as the `:style/indent` metadata specified by [cider-nrepl][].
302307
For more details on writing indent specifications, see
303308
[this document](https://docs.cider.mx/cider/indent_spec.html).
304-
The only difference is that you're allowed to use lists instead of vectors.
305309

306310
Backtracking is controlled by `clojure-use-backtracking-indent`
307311
(default `t`) and limited to `clojure-max-backtracking` levels
308312
(default 3). Disabling backtracking will break indentation for
309-
all forms with list-based specs.
313+
all forms with multi-rule specs.
310314

311315
The indentation of [special arguments](https://docs.cider.mx/cider/indent_spec.html#special-arguments) is controlled by
312316
`clojure-special-arg-indent-factor`, which by default indents special arguments

0 commit comments

Comments
 (0)