Skip to content

Support alignment for reader conditionals #486

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 27, 2018
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#483](https://github.com/clojure-emacs/clojure-mode/issues/483): Support alignment for reader conditionals, with the new `clojure-align-reader-conditionals` user option.

## 5.9.1 (2018-08-27)

* [#485](https://github.com/clojure-emacs/clojure-mode/issues/485): Fix a regression in `end-f-defun`.
Expand Down
78 changes: 48 additions & 30 deletions clojure-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1090,6 +1090,12 @@ will align the values like this:
:safe #'booleanp
:type 'boolean)

(defcustom clojure-align-reader-conditionals nil
"Whether to align reader conditionals, as if they were maps."
:package-version '(clojure-mode . "5.10")
:safe #'booleanp
:type 'boolean)

(defcustom clojure-align-binding-forms
'("let" "when-let" "when-some" "if-let" "if-some" "binding" "loop"
"doseq" "for" "with-open" "with-local-vars" "with-redefs")
Expand All @@ -1104,6 +1110,10 @@ will align the values like this:
:safe #'listp
:type '(repeat string))

(defvar clojure--beginning-of-reader-conditional-regexp
"#\\?@(\\|#\\?("
"Regexp denoting the beginning of a reader conditional.")

(defun clojure--position-for-alignment ()
"Non-nil if the sexp around point should be automatically aligned.
This function expects to be called immediately after an
Expand All @@ -1118,32 +1128,36 @@ For instance, in a map literal point is left immediately before
the first key; while, in a let-binding, point is left inside the
binding vector and immediately before the first binding
construct."
;; Are we in a map?
(or (and (eq (char-before) ?{)
(not (eq (char-before (1- (point))) ?\#)))
;; Are we in a cond form?
(let* ((fun (car (member (thing-at-point 'symbol) clojure-align-cond-forms)))
(method (and fun (clojure--get-indent-method fun)))
;; The number of special arguments in the cond form is
;; the number of sexps we skip before aligning.
(skip (cond ((numberp method) method)
((null method) 0)
((sequencep method) (elt method 0)))))
(when (and fun (numberp skip))
(clojure-forward-logical-sexp skip)
(comment-forward (point-max))
fun)) ; Return non-nil (the var name).
;; Are we in a let-like form?
(when (member (thing-at-point 'symbol)
clojure-align-binding-forms)
;; Position inside the binding vector.
(clojure-forward-logical-sexp)
(backward-sexp)
(when (eq (char-after) ?\[)
(forward-char 1)
(comment-forward (point-max))
;; Return non-nil.
t))))
(let ((point (point)))
;; Are we in a map?
(or (and (eq (char-before) ?{)
(not (eq (char-before (1- point)) ?\#)))
;; Are we in a reader conditional?
(and clojure-align-reader-conditionals
(looking-back clojure--beginning-of-reader-conditional-regexp (- (point) 4)))
;; Are we in a cond form?
(let* ((fun (car (member (thing-at-point 'symbol) clojure-align-cond-forms)))
(method (and fun (clojure--get-indent-method fun)))
;; The number of special arguments in the cond form is
;; the number of sexps we skip before aligning.
(skip (cond ((numberp method) method)
((null method) 0)
((sequencep method) (elt method 0)))))
(when (and fun (numberp skip))
(clojure-forward-logical-sexp skip)
(comment-forward (point-max))
fun)) ; Return non-nil (the var name).
;; Are we in a let-like form?
(when (member (thing-at-point 'symbol)
clojure-align-binding-forms)
;; Position inside the binding vector.
(clojure-forward-logical-sexp)
(backward-sexp)
(when (eq (char-after) ?\[)
(forward-char 1)
(comment-forward (point-max))
;; Return non-nil.
t)))))

(defun clojure--find-sexp-to-align (end)
"Non-nil if there's a sexp ahead to be aligned before END.
Expand All @@ -1152,10 +1166,14 @@ Place point as in `clojure--position-for-alignment'."
(let ((found))
(while (and (not found)
(search-forward-regexp
(concat "{\\|(" (regexp-opt
(append clojure-align-binding-forms
clojure-align-cond-forms)
'symbols))
(concat (when clojure-align-reader-conditionals
(concat clojure--beginning-of-reader-conditional-regexp
"\\|"))
"{\\|("
(regexp-opt
(append clojure-align-binding-forms
clojure-align-cond-forms)
'symbols))
end 'noerror))

(let ((ppss (syntax-ppss)))
Expand Down
25 changes: 24 additions & 1 deletion test/clojure-mode-indentation-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,8 @@ x
"Verify that all FORMs correspond to a properly indented sexps."
(declare (indent defun))
`(ert-deftest ,(intern (format "test-align-%s" name)) ()
(let ((clojure-align-forms-automatically t))
(let ((clojure-align-forms-automatically t)
(clojure-align-reader-conditionals t))
,@(mapcar (lambda (form)
`(with-temp-buffer
(clojure-mode)
Expand Down Expand Up @@ -596,6 +597,28 @@ x
:b {:a :a,
:aa :a}}")

(def-full-align-test reader-conditional
"#?(:clj 2
:cljs 2)")

(def-full-align-test reader-conditional-splicing
"#?@(:clj [2]
:cljs [2])")

(ert-deftest reader-conditional-alignment-disabled-by-default ()
(let ((content "#?(:clj 2\n :cljs 2)"))
(with-temp-buffer
(clojure-mode)
(insert content)
(call-interactively #'clojure-align)
(should (string= (buffer-string) content)))
(with-temp-buffer
(clojure-mode)
(setq-local clojure-align-reader-conditionals t)
(insert content)
(call-interactively #'clojure-align)
(should-not (string= (buffer-string) content)))))

(ert-deftest clojure-align-remove-extra-commas ()
(with-temp-buffer
(clojure-mode)
Expand Down