6767(require 'cl-lib )
6868(require 'imenu )
6969(require 'newcomment )
70+ (require 'align )
7071
7172(declare-function lisp-fill-paragraph " lisp-mode" (&optional justify))
7273
@@ -147,6 +148,7 @@ Out-of-the box clojure-mode understands lein, boot and gradle."
147148(defvar clojure-mode-map
148149 (let ((map (make-sparse-keymap )))
149150 (define-key map (kbd " C-:" ) #'clojure-toggle-keyword-string )
151+ (define-key map (kbd " C-c SPC" ) #'clojure-align )
150152 (easy-menu-define clojure-mode-menu map " Clojure Mode Menu"
151153 '(" Clojure"
152154 [" Toggle between string & keyword" clojure-toggle-keyword-string]
@@ -266,6 +268,7 @@ instead of to `clojure-mode-map'."
266268 (setq-local comment-start-skip
267269 " \\ (\\ (^\\ |[^\\\\ \n ]\\ )\\ (\\\\\\\\\\ )*\\ )\\ (;+\\ |#|\\ ) *" )
268270 (setq-local indent-line-function #'clojure-indent-line )
271+ (setq-local indent-region-function #'clojure-indent-region )
269272 (setq-local lisp-indent-function #'clojure-indent-function )
270273 (setq-local lisp-doc-string-elt-property 'clojure-doc-string-elt )
271274 (setq-local parse-sexp-ignore-comments t )
@@ -703,6 +706,147 @@ point) to check."
703706(put 'definline 'clojure-doc-string-elt 2 )
704707(put 'defprotocol 'clojure-doc-string-elt 2 )
705708
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+
706850(defun clojure-indent-line ()
707851 " Indent current line as Clojure code."
708852 (if (clojure-in-docstring-p)
@@ -1191,6 +1335,7 @@ Sexps that don't represent code are ^metadata or #reader.macros."
11911335This will skip over sexps that don't represent objects, so that ^hints and
11921336#reader.macros are considered part of the following sexp."
11931337 (interactive " p" )
1338+ (unless n (setq n 1 ))
11941339 (if (< n 0 )
11951340 (clojure-backward-logical-sexp (- n))
11961341 (let ((forward-sexp-function nil ))
@@ -1206,6 +1351,7 @@ This will skip over sexps that don't represent objects, so that ^hints and
12061351This will skip over sexps that don't represent objects, so that ^hints and
12071352#reader.macros are considered part of the following sexp."
12081353 (interactive " p" )
1354+ (unless n (setq n 1 ))
12091355 (if (< n 0 )
12101356 (clojure-forward-logical-sexp (- n))
12111357 (let ((forward-sexp-function nil ))
0 commit comments