From 8ed99ba911e77b71fb78a750d8a90a31aeadc6f3 Mon Sep 17 00:00:00 2001 From: vemv Date: Fri, 24 Aug 2018 12:55:15 +0200 Subject: [PATCH] Support alignment for reader conditionals Fixes #483 --- CHANGELOG.md | 8 +++ clojure-mode.el | 94 ++++++++++++++++----------- test/clojure-mode-indentation-test.el | 25 ++++++- test/clojure-mode-sexp-test.el | 16 ++++- 4 files changed, 104 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e37567a7..d4ba472f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## 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`. + ## 5.9.0 (2018-08-18) ### Changes diff --git a/clojure-mode.el b/clojure-mode.el index 27313a7f..b8928821 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -10,7 +10,7 @@ ;; Artur Malabarba ;; URL: http://github.com/clojure-emacs/clojure-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 5.9.0 +;; Version: 5.9.1 ;; Package-Requires: ((emacs "25.1")) ;; This file is not part of GNU Emacs. @@ -439,7 +439,7 @@ ENDP and DELIM." (t))))) (defconst clojure--collection-tag-regexp "#\\(::[a-zA-Z0-9._-]*\\|:?\\([a-zA-Z0-9._-]+/\\)?[a-zA-Z0-9._-]+\\)" - "Collection reader macro tag regexp. + "Collection reader macro tag regexp. It is intended to check for allowed strings that can come before a collection literal (e.g. '[]' or '{}'), as reader macro tags. This includes #fully.qualified/my-ns[:kw val] and #::my-ns{:kw @@ -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") @@ -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 @@ -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. @@ -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))) @@ -1955,7 +1973,8 @@ This will skip over sexps that don't represent objects, so that ^hints and (clojure-forward-logical-sexp 1) (clojure-backward-logical-sexp 1) (looking-at-p first-form)) - (scan-error nil))) + (scan-error nil) + (end-of-buffer nil))) (defun clojure-sexp-starts-until-position (position) "Return the starting points for forms before POSITION. @@ -1992,10 +2011,11 @@ testing, give an easy way to turn this new behavior off." (setq haystack (cdr haystack)))) found)) -(defun clojure-beginning-of-defun-function () +(defun clojure-beginning-of-defun-function (&optional n) "Go to top level form. Set as `beginning-of-defun-function' so that these generic -operators can be used." +operators can be used. Given a positive N it will do it that +many times." (let ((beginning-of-defun-function nil)) (if (and clojure-toplevel-inside-comment-form (clojure-top-level-form-p "comment")) @@ -2013,8 +2033,8 @@ operators can be used." (clojure-sexp-starts-until-position clojure-comment-end)))) (progn (goto-char sexp-start) t) - (progn (beginning-of-defun) t)))) - (progn (beginning-of-defun) t)))) + (beginning-of-defun n)))) + (beginning-of-defun n)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; diff --git a/test/clojure-mode-indentation-test.el b/test/clojure-mode-indentation-test.el index 71e4ab5f..6f22503f 100644 --- a/test/clojure-mode-indentation-test.el +++ b/test/clojure-mode-indentation-test.el @@ -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) @@ -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) diff --git a/test/clojure-mode-sexp-test.el b/test/clojure-mode-sexp-test.el index 1faf1039..40f861c0 100644 --- a/test/clojure-mode-sexp-test.el +++ b/test/clojure-mode-sexp-test.el @@ -65,7 +65,21 @@ and point left there." (wrong))" (let ((clojure-toplevel-inside-comment-form t)) (beginning-of-defun)) - (should (looking-at-p "(right)")))) + (should (looking-at-p "[[:space:]]*(right)")))) + +(ert-deftest test-clojure-end-of-defun-function () + (clojure-buffer-with-text + " +(first form) +| +(second form) + +(third form)" + + (end-of-defun) + (backward-char) + (should (looking-back "(second form)")))) + (ert-deftest test-sexp-with-commas () (with-temp-buffer