Skip to content

Commit a97afbe

Browse files
committed
Merge pull request #363 from clojure-emacs/indent-style
Offer a third indentation style
2 parents 1c5f3fb + cbb3ef0 commit a97afbe

File tree

4 files changed

+184
-32
lines changed

4 files changed

+184
-32
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
* [#360](https://github.com/clojure-emacs/clojure-mode/issues/360): `clojure-align` now reindents after aligning, which also fixes an issue with nested alignings.
88

9+
### New features
10+
11+
* [#362](https://github.com/clojure-emacs/clojure-mode/issues/362): New custom option `clojure-indent-style` offers 3 different ways to indent code.
12+
913
## 5.1.0 (04/01/2015)
1014

1115
### New features

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,49 @@ The default indentation rules in `clojure-mode` are derived from the
7070
[community Clojure Style Guide](https://github.com/bbatsov/clojure-style-guide).
7171
Please, refer to the guide for the general Clojure indentation rules.
7272

73+
#### Indentation of function forms
74+
75+
The indentation of function forms is configured by the variable
76+
`clojure-indent-style`. It takes three possible values:
77+
78+
- `:always-align` (the default)
79+
80+
```clj
81+
(some-function
82+
10
83+
1
84+
2)
85+
(some-function 10
86+
1
87+
2)
88+
```
89+
90+
- `:always-indent`
91+
92+
```clj
93+
(some-function
94+
10
95+
1
96+
2)
97+
(some-function 10
98+
1
99+
2)
100+
```
101+
102+
- `:align-arguments`
103+
104+
```clj
105+
(some-function
106+
10
107+
1
108+
2)
109+
(some-function 10
110+
1
111+
2)
112+
```
113+
114+
#### Indentation of macro forms
115+
73116
The indentation of special forms and macros with bodies is controlled via
74117
`put-clojure-indent`, `define-clojure-indent` and `clojure-backtracking-indent`.
75118
Nearly all special forms and built-in macros with bodies have special indentation

clojure-mode.el

Lines changed: 110 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,56 @@
9595
"Face used to font-lock interop method names (camelCase)."
9696
:package-version '(clojure-mode . "3.0.0"))
9797

98-
(defcustom clojure-defun-style-default-indent nil
99-
"When non-nil, use default indenting for functions and macros.
100-
Otherwise check `define-clojure-indent' and `put-clojure-indent'."
101-
:type 'boolean
102-
:safe 'booleanp)
98+
(defcustom clojure-indent-style :always-align
99+
"Indentation style to use for function forms and macro forms.
100+
There are two cases of interest configured by this variable.
101+
102+
- Case (A) is when at least one function argument is on the same
103+
line as the function name.
104+
- Case (B) is the opposite (no arguments are on the same line as
105+
the function name). Note that the body of macros is not
106+
affected by this variable, it is always indented by
107+
`lisp-body-indent' (default 2) spaces.
108+
109+
Note that this variable configures the indentation of function
110+
forms (and function-like macros), it does not affect macros that
111+
already use special indentation rules.
112+
113+
The possible values for this variable are keywords indicating how
114+
to indent function forms.
115+
116+
`:always-align' - Follow the same rules as `lisp-mode'. All
117+
args are vertically aligned with the first arg in case (A),
118+
and vertically aligned with the function name in case (B).
119+
For instance:
120+
(reduce merge
121+
some-coll)
122+
(reduce
123+
merge
124+
some-coll)
125+
126+
`:always-indent' - All args are indented like a macro body.
127+
(reduce merge
128+
some-coll)
129+
(reduce
130+
merge
131+
some-coll)
132+
133+
`:align-arguments' - Case (A) is indented like `lisp', and
134+
case (B) is indented like a macro body.
135+
(reduce merge
136+
some-coll)
137+
(reduce
138+
merge
139+
some-coll)"
140+
:type '(choice (const :tag "Same as `lisp-mode'" lisp)
141+
(const :tag "Indent like a macro body" always-body)
142+
(const :tag "Indent like a macro body unless first arg is on the same line"
143+
body-unless-same-line))
144+
:package-version '(clojure-mode . "5.2.0"))
145+
146+
(define-obsolete-variable-alias 'clojure-defun-style-default-indent
147+
'clojure-indent-style "5.2.0")
103148

104149
(defcustom clojure-use-backtracking-indent t
105150
"When non-nil, enable context sensitive indentation."
@@ -950,25 +995,47 @@ spec."
950995
(let ((function (thing-at-point 'symbol)))
951996
(clojure--get-indent-method function))))
952997

953-
(defun clojure--normal-indent (last-sexp)
998+
(defun clojure--normal-indent (last-sexp indent-mode)
954999
"Return the normal indentation column for a sexp.
955-
LAST-SEXP is the start of the previous sexp."
1000+
Point should be after the open paren of the _enclosing_ sexp, and
1001+
LAST-SEXP is the start of the previous sexp (immediately before
1002+
the sexp being indented). INDENT-MODE is any of the values
1003+
accepted by `clojure-indent-style'."
9561004
(goto-char last-sexp)
9571005
(forward-sexp 1)
9581006
(clojure-backward-logical-sexp 1)
9591007
(let ((last-sexp-start nil))
960-
(unless (ignore-errors
961-
(while (string-match
962-
"[^[:blank:]]"
963-
(buffer-substring (line-beginning-position) (point)))
964-
(setq last-sexp-start (prog1 (point)
965-
(forward-sexp -1))))
966-
t)
967-
;; If the last sexp was on the same line.
968-
(when (and last-sexp-start
969-
(> (line-end-position) last-sexp-start))
970-
(goto-char last-sexp-start)))
971-
(current-column)))
1008+
(if (ignore-errors
1009+
;; `backward-sexp' until we reach the start of a sexp that is the
1010+
;; first of its line (the start of the enclosing sexp).
1011+
(while (string-match
1012+
"[^[:blank:]]"
1013+
(buffer-substring (line-beginning-position) (point)))
1014+
(setq last-sexp-start (prog1 (point)
1015+
(forward-sexp -1))))
1016+
t)
1017+
;; Here we have found an arg before the arg we're indenting which is at
1018+
;; the start of a line. Every mode simply aligns on this case.
1019+
(current-column)
1020+
;; Here we have reached the start of the enclosing sexp (point is now at
1021+
;; the function name), so the behaviour depends on INDENT-MODE and on
1022+
;; whether there's also an argument on this line (case A or B).
1023+
(let ((case-a ; The meaning of case-a is explained in `clojure-indent-style'.
1024+
(and last-sexp-start
1025+
(< last-sexp-start (line-end-position)))))
1026+
(cond
1027+
;; For compatibility with the old `clojure-defun-style-default-indent', any
1028+
;; value other than these 3 is equivalent to `always-body'.
1029+
((not (memq indent-mode '(:always-align :align-arguments nil)))
1030+
(+ (current-column) lisp-body-indent -1))
1031+
;; There's an arg after the function name, so align with it.
1032+
(case-a (goto-char last-sexp-start)
1033+
(current-column))
1034+
;; Not same line.
1035+
((eq indent-mode :align-arguments)
1036+
(+ (current-column) lisp-body-indent -1))
1037+
;; Finally, just align with the function name.
1038+
(t (current-column)))))))
9721039

9731040
(defun clojure--not-function-form-p ()
9741041
"Non-nil if form at point doesn't represent a function call."
@@ -982,6 +1049,9 @@ LAST-SEXP is the start of the previous sexp."
9821049
;; Car of form is not a symbol.
9831050
(not (looking-at ".\\(?:\\sw\\|\\s_\\)"))))
9841051

1052+
;; Check the general context, and provide indentation for data structures and
1053+
;; special macros. If current form is a function (or non-special macro),
1054+
;; delegate indentation to `clojure--normal-indent'.
9851055
(defun clojure-indent-function (indent-point state)
9861056
"When indenting a line within a function call, indent properly.
9871057
@@ -1013,6 +1083,7 @@ This function also returns nil meaning don't specify the indentation."
10131083
;; Function or macro call.
10141084
(forward-char 1)
10151085
(let ((method (clojure--find-indent-spec))
1086+
(last-sexp calculate-lisp-indent-last-sexp)
10161087
(containing-form-column (1- (current-column))))
10171088
(pcase method
10181089
((or (pred integerp) `(,method))
@@ -1028,26 +1099,35 @@ This function also returns nil meaning don't specify the indentation."
10281099
;; indentation as if there were an extra sexp at point.
10291100
(scan-error (cl-incf pos)))
10301101
(cond
1102+
;; The first non-special arg. Rigidly reduce indentation.
10311103
((= pos (1+ method))
10321104
(+ lisp-body-indent containing-form-column))
1105+
;; Further non-special args, align with the arg above.
10331106
((> pos (1+ method))
1034-
(clojure--normal-indent calculate-lisp-indent-last-sexp))
1107+
(clojure--normal-indent last-sexp :always-align))
1108+
;; Special arg. Rigidly indent with a large indentation.
10351109
(t
10361110
(+ (* 2 lisp-body-indent) containing-form-column)))))
10371111
(`:defn
10381112
(+ lisp-body-indent containing-form-column))
10391113
((pred functionp)
10401114
(funcall method indent-point state))
1041-
((and `nil
1042-
(guard (let ((function (thing-at-point 'sexp)))
1043-
(or (and clojure-defun-style-default-indent
1044-
;; largely to preserve useful alignment of :require, etc in ns
1045-
(not (string-match "^:" function)))
1046-
(and (string-match "\\`\\(?:\\S +/\\)?\\(def[a-z]*\\|with-\\)"
1047-
function)
1048-
(not (string-match "\\`default" (match-string 1 function))))))))
1049-
(+ lisp-body-indent containing-form-column))
1050-
(_ (clojure--normal-indent calculate-lisp-indent-last-sexp))))))
1115+
;; No indent spec, do the default.
1116+
(`nil
1117+
(let ((function (thing-at-point 'symbol)))
1118+
(cond
1119+
;; Preserve useful alignment of :require (and friends) in `ns' forms.
1120+
((and function (string-match "^:" function))
1121+
(clojure--normal-indent last-sexp :align-arguments))
1122+
;; This is should be identical to the :defn above.
1123+
((and function
1124+
(string-match "\\`\\(?:\\S +/\\)?\\(def[a-z]*\\|with-\\)"
1125+
function)
1126+
(not (string-match "\\`default" (match-string 1 function))))
1127+
(+ lisp-body-indent containing-form-column))
1128+
;; Finally, nothing special here, just respect the user's
1129+
;; preference.
1130+
(t (clojure--normal-indent last-sexp clojure-indent-style)))))))))
10511131

10521132
;;; Setting indentation
10531133
(defun put-clojure-indent (sym indent)

test/clojure-mode-indentation-test.el

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ values of customisable variables."
5858
(let ((fname (intern (format "indentation/%s" description))))
5959
`(ert-deftest ,fname ()
6060
(let* ((after ,after)
61+
(clojure-indent-style :always-align)
6162
(expected-cursor-pos (1+ (s-index-of "|" after)))
6263
(expected-state (delete ?| after))
6364
,@var-bindings)
@@ -221,16 +222,20 @@ values of customisable variables."
221222

222223

223224
;;; Backtracking indent
224-
(defmacro def-full-indent-test (name &rest forms)
225+
(defmacro def-full-indent-test (name &optional style &rest forms)
225226
"Verify that all FORMs correspond to a properly indented sexps."
226227
(declare (indent 1))
228+
(when (stringp style)
229+
(setq forms (cons style forms))
230+
(setq style :always-align))
227231
`(ert-deftest ,(intern (format "test-backtracking-%s" name)) ()
228232
(progn
229233
,@(mapcar (lambda (form)
230234
`(with-temp-buffer
231235
(clojure-mode)
232236
(insert "\n" ,(replace-regexp-in-string "\n +" "\n " form))
233-
(indent-region (point-min) (point-max))
237+
(let ((clojure-indent-style ,style))
238+
(indent-region (point-min) (point-max)))
234239
(should (equal (buffer-string)
235240
,(concat "\n" form)))))
236241
forms))))
@@ -403,6 +408,26 @@ x
403408
2
404409
3))")
405410

411+
(def-full-indent-test align-arguments
412+
:align-arguments
413+
"(some-function
414+
10
415+
1
416+
2)"
417+
"(some-function 10
418+
1
419+
2)")
420+
421+
(def-full-indent-test always-indent
422+
:always-indent
423+
"(some-function
424+
10
425+
1
426+
2)"
427+
"(some-function 10
428+
1
429+
2)")
430+
406431
;;; Alignment
407432
(defmacro def-full-align-test (name &rest forms)
408433
"Verify that all FORMs correspond to a properly indented sexps."

0 commit comments

Comments
 (0)