67
67
(require 'cl-lib )
68
68
(require 'imenu )
69
69
(require 'newcomment )
70
+ (require 'align )
70
71
71
72
(declare-function lisp-fill-paragraph " lisp-mode" (&optional justify))
72
73
@@ -147,6 +148,7 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
147
148
(defvar clojure-mode-map
148
149
(let ((map (make-sparse-keymap )))
149
150
(define-key map (kbd " C-:" ) #'clojure-toggle-keyword-string )
151
+ (define-key map (kbd " C-c SPC" ) #'clojure-align )
150
152
(easy-menu-define clojure-mode-menu map " Clojure Mode Menu"
151
153
'(" Clojure"
152
154
[" Toggle between string & keyword" clojure-toggle-keyword-string]
@@ -266,6 +268,7 @@ instead of to `clojure-mode-map'."
266
268
(setq-local comment-start-skip
267
269
" \\ (\\ (^\\ |[^\\\\ \n ]\\ )\\ (\\\\\\\\\\ )*\\ )\\ (;+\\ |#|\\ ) *" )
268
270
(setq-local indent-line-function #'clojure-indent-line )
271
+ (setq-local indent-region-function #'clojure-indent-region )
269
272
(setq-local lisp-indent-function #'clojure-indent-function )
270
273
(setq-local lisp-doc-string-elt-property 'clojure-doc-string-elt )
271
274
(setq-local parse-sexp-ignore-comments t )
@@ -703,6 +706,147 @@ point) to check."
703
706
(put 'definline 'clojure-doc-string-elt 2 )
704
707
(put 'defprotocol 'clojure-doc-string-elt 2 )
705
708
709
+ ; ;; Vertical alignment
710
+ (defcustom clojure-align-forms-automatically nil
711
+ " If non-nil, vertically align some forms automatically.
712
+ Automatically means it is done as part of indenting code. This
713
+ applies to binding forms (`clojure-align-binding-forms' ), to cond
714
+ forms (`clojure-align-cond-forms' ) and to map literals. For
715
+ instance, selecting a map a hitting \\ <clojure-mode-map>`\\[indent-for-tab-command]' will align the values
716
+ like this:
717
+ {:some-key 10
718
+ :key2 20}"
719
+ :package-version '(clojure-mode . " 5.1" )
720
+ :type 'boolean )
721
+
722
+ (defcustom clojure-align-binding-forms '(" let" " when-let" " if-let" " binding" " loop" " with-open" )
723
+ " List of strings matching forms that have binding forms."
724
+ :package-version '(clojure-mode . " 5.1" )
725
+ :type '(repeat string))
726
+
727
+ (defcustom clojure-align-cond-forms '(" condp" " cond" " cond->" " cond->>" " case" )
728
+ " List of strings identifying cond-like forms."
729
+ :package-version '(clojure-mode . " 5.1" )
730
+ :type '(repeat string))
731
+
732
+ (defun clojure--position-for-alignment ()
733
+ " Non-nil if the sexp around point should be automatically aligned.
734
+ This function expects to be called immediately after an
735
+ open-brace or after the function symbol in a function call.
736
+
737
+ First check if the sexp around point is a map literal, or is a
738
+ call to one of the vars listed in `clojure-align-cond-forms' . If
739
+ it isn't, return nil. If it is, return non-nil and place point
740
+ immediately before the forms that should be aligned.
741
+
742
+ For instance, in a map literal point is left immediately before
743
+ the first key; while, in a let-binding, point is left inside the
744
+ binding vector and immediately before the first binding
745
+ construct."
746
+ ; ; Are we in a map?
747
+ (or (and (eq (char-before ) ?{ )
748
+ (not (eq (char-before (1- (point ))) ?\# )))
749
+ ; ; Are we in a cond form?
750
+ (let* ((fun (car (member (thing-at-point 'symbol ) clojure-align-cond-forms)))
751
+ (method (and fun (clojure--get-indent-method fun)))
752
+ ; ; The number of special arguments in the cond form is
753
+ ; ; the number of sexps we skip before aligning.
754
+ (skip (cond ((numberp method) method)
755
+ ((sequencep method) (elt method 0 )))))
756
+ (when (numberp skip)
757
+ (clojure-forward-logical-sexp skip)
758
+ (comment-forward (point-max ))
759
+ fun)) ; Return non-nil (the var name).
760
+ ; ; Are we in a let-like form?
761
+ (when (member (thing-at-point 'symbol )
762
+ clojure-align-binding-forms)
763
+ ; ; Position inside the binding vector.
764
+ (clojure-forward-logical-sexp)
765
+ (backward-sexp )
766
+ (when (eq (char-after ) ?\[ )
767
+ (forward-char 1 )
768
+ (comment-forward (point-max ))
769
+ ; ; Return non-nil.
770
+ t ))))
771
+
772
+ (defun clojure--find-sexp-to-align (end )
773
+ " Non-nil if there's a sexp ahead to be aligned before END.
774
+ Place point as in `clojure--position-for-alignment' ."
775
+ ; ; Look for a relevant sexp.
776
+ (let ((found))
777
+ (while (and (not found)
778
+ (search-forward-regexp
779
+ (concat " {\\ |(" (regexp-opt
780
+ (append clojure-align-binding-forms
781
+ clojure-align-cond-forms)
782
+ 'symbols ))
783
+ end 'noerror ))
784
+
785
+ (let ((ppss (syntax-ppss )))
786
+ ; ; If we're in a string or comment.
787
+ (unless (or (elt ppss 3 )
788
+ (elt ppss 4 ))
789
+ ; ; Only stop looking if we successfully position
790
+ ; ; the point.
791
+ (setq found (clojure--position-for-alignment)))))
792
+ found))
793
+
794
+ (defun clojure--search-whitespace-after-next-sexp (&optional bound _noerror )
795
+ " Move point after all whitespace after the next sexp.
796
+ Set the match data group 1 to be this region of whitespace and
797
+ return point."
798
+ (unwind-protect
799
+ (ignore-errors
800
+ (clojure-forward-logical-sexp 1 )
801
+ (search-forward-regexp " \\ ( *\\ )" bound)
802
+ (pcase (syntax-after (point ))
803
+ ; ; End-of-line, try again on next line.
804
+ (`(12 ) (clojure--search-whitespace-after-next-sexp bound))
805
+ ; ; Closing paren, stop here.
806
+ (`(5 . , _ ) nil )
807
+ ; ; Anything else is something to align.
808
+ (_ (point ))))
809
+ (when (and bound (> (point ) bound))
810
+ (goto-char bound))))
811
+
812
+ (defun clojure-align (beg end )
813
+ " Vertically align the contents of the sexp around point.
814
+ If region is active, align it. Otherwise, align everything in the
815
+ current top-level sexp.
816
+ When called from lisp code align everything between BEG and END."
817
+ (interactive (if (use-region-p )
818
+ (list (region-beginning ) (region-end ))
819
+ (save-excursion
820
+ (let ((end (progn (end-of-defun )
821
+ (point ))))
822
+ (clojure-backward-logical-sexp)
823
+ (list (point ) end)))))
824
+ (save-excursion
825
+ (goto-char beg)
826
+ (while (clojure--find-sexp-to-align end)
827
+ (align-region (point )
828
+ (save-excursion
829
+ (backward-up-list )
830
+ (forward-sexp 1 )
831
+ (point ))
832
+ nil
833
+ '((clojure-align (regexp . clojure--search-whitespace-after-next-sexp)
834
+ (group . 1 )
835
+ (repeat . t )))
836
+ nil ))))
837
+
838
+ ; ;; Indentation
839
+ (defun clojure-indent-region (beg end )
840
+ " Like `indent-region' , but also maybe align forms.
841
+ Forms between BEG and END are aligned according to
842
+ `clojure-align-forms-automatically' ."
843
+ (prog1 (let ((indent-region-function nil ))
844
+ (indent-region beg end))
845
+ (when clojure-align-forms-automatically
846
+ (condition-case er
847
+ (clojure-align beg end)
848
+ (scan-error nil )))))
849
+
706
850
(defun clojure-indent-line ()
707
851
" Indent current line as Clojure code."
708
852
(if (clojure-in-docstring-p)
@@ -1191,6 +1335,7 @@ Sexps that don't represent code are ^metadata or #reader.macros."
1191
1335
This will skip over sexps that don't represent objects, so that ^hints and
1192
1336
#reader.macros are considered part of the following sexp."
1193
1337
(interactive " p" )
1338
+ (unless n (setq n 1 ))
1194
1339
(if (< n 0 )
1195
1340
(clojure-backward-logical-sexp (- n))
1196
1341
(let ((forward-sexp-function nil ))
@@ -1206,6 +1351,7 @@ This will skip over sexps that don't represent objects, so that ^hints and
1206
1351
This will skip over sexps that don't represent objects, so that ^hints and
1207
1352
#reader.macros are considered part of the following sexp."
1208
1353
(interactive " p" )
1354
+ (unless n (setq n 1 ))
1209
1355
(if (< n 0 )
1210
1356
(clojure-forward-logical-sexp (- n))
1211
1357
(let ((forward-sexp-function nil ))
0 commit comments