clojure-emacs / clojure-mode

Emacs support for the Clojure(Script) programming language
910 stars 246 forks source link

Indentation of `clojure.test/are` #548

Open joncol opened 4 years ago

joncol commented 4 years ago

Expected behavior

I was expecting clojure-mode's indentation to behave similar to cider-format and lein cljfmt. Is there some way to configure clojure-mode to indent the same way as cider-format?

Actual behavior

The macro clojure.test/are is indented one way with M-x cider-format (and lein cljfmt), but another way when using clojure-mode's indentation rules (with aggressive-indent-mode). The latter uses one space less than the former for the second argument to are (when it is on a new line).

Steps to reproduce the problem

The code is formatted as following using clojure-mode indentation:

(deftest are-test
  (are [x y]
      (= x y)
    1 1
    2 2))

And as following when using cider-format indentation (note the extra space on line 3):

((deftest are-test
  (are [x y]
       (= x y)
    1 1
    2 2))

Environment & Version information

clojure-mode version information

clojure-mode (version 5.11.0)

Emacs version

GNU Emacs 27.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.12, cairo version 1.17.3) of 2019-11-26

Operating system

Arch Linux

svantevonerichsen6906 commented 4 years ago

I'd actually expect the clojure-mode result (not alignment, but two levels of indentation to separate it from the body).

joncol commented 4 years ago

I'd actually expect the clojure-mode result (not alignment, but two levels of indentation to separate it from the body).

I'm not sure I understand what you mean. But it would be best if there was some way to configure this...

j-cr commented 4 years ago

Seems like it's not specific to are, all n-arg indent specs are broken(?):

;; in emacs:
(define-clojure-indent
  (foo 3))

;; in clojure:
(foo 1
    2
    3
  4
  5)

;; in emacs:
(define-clojure-indent
  (foo nil))

;; in clojure:
(foo 1
     2
     3
     4
     5)
j-cr commented 4 years ago

This change to clojure-indent-function does the trick:

modified   clojure-mode.el
@@ -1511,7 +1511,8 @@ This function also returns nil meaning don't specify the indentation."
              (clojure--normal-indent last-sexp 'always-align))
             ;; Special arg. Rigidly indent with a large indentation.
             (t
-             (+ (* 2 lisp-body-indent) containing-form-column)))))
+             (+ 1 (* 2 lisp-body-indent) containing-form-column)
+             ))))
         (`:defn
          (+ lisp-body-indent containing-form-column))
         ((pred functionp)

Though I suspect a proper fix should take into account the value of clojure-indent-style var. Docstring for clojure-indent-function states:

- an integer N, meaning indent the first N arguments specially
  like ordinary function arguments and then indent any further
  arguments like a body;

...and how 'ordinary function arguments' are indented is controlled by clojure-indent-style: if it's always-align or align-arguments, then the first N arguments should be on the same column; if it's always-indent however, then logically according to the spec ("All args are indented like a macro body") the first N arguments should be indented the same as all the other ones, i.e. it should be a no-op.

I'm not sure if clojure-indent-function is supposed to be used that way though; chances are I'm overthinking this?

In any case, with the provided patch:

;; in emacs:
(define-clojure-indent
  (foo 3))

;; in clojure: 
(foo 1
     2
     3
  4
  5) 
nfedyashev commented 4 years ago

hi @j-cr

Thanks for posting this diff! I was to apply it to my (spac)emacs installation and it failed. Can you please explain how to apply this diff?

I've tried the following process:

  1. found that file( ~/.emacs.d/elpa/develop/clojure-mode-20200813.639/clojure-mode.el )
  2. applied the fix
  3. executed (spacemacs/recompile-elpa) which results in several warnings.
  4. After emacs restart, regular modes stopped working - e.g. clj files are opened in fundamental(plain text) mode.

I've rolled back that diff and recompiled elpa. Everything seems to be working again but I was trying to fix this issue with clojure.test/are form weird formatting.

Seems like it's not specific to are, all n-arg indent specs are broken(?): From what I see, the biggest(only?) issue is with are form because If I rename it to any other 3-character form(e.g. foo) formatting suddenly works again.

j-cr commented 4 years ago

I don't use spacemacs so I don't know. Simply finding that function, changing it and evaluating it should work.

nfedyashev commented 4 years ago

@j-cr thanks!

andreyorst commented 3 years ago

I'd actually expect the clojure-mode result (not alignment, but two levels of indentation to separate it from the body).

I'm not sure I understand what you mean.

@joncol, svantevonerichsen6906 meant that this is correct formatting.

Clojure mode indents special arguments by fixed amount of spaces, which is twice as large as indentation of ordinary macro parameter. Which means that when you say (define-clojure-indent (foo 3)) you mean that first 3 arguments to foo should be considered special. These arguments are not aligned to the first one, but use 4 space indentation to indicate that these are indeed special args.

Similarly, if you put let binding vector onto a separate line, you'll see that it is indented by four spaces, where the body is indented by 2:

(let
    [a 10]
  a)

It may actually look that vector is aligned with where the first argument should go, but it's just a coincidence. If you use a longer macros like (define-clojure-indent (foo-bar-baz 2)) you'll see this:

(foo-bar-baz a
    b
  c)

Which helps seeing that a, and b are special args, and c is body form.


patch in https://github.com/clojure-emacs/clojure-mode/issues/548#issuecomment-568907128 introduces incorrect formatting, by making all special args indented by 5 spaces instead of 4, thus making those align with forms that have 3 letter long names, but it will still not work with foo-bar-baz macros, which I assume is what you want