Skip to content

Commit 27c2a6f

Browse files
committed
Merge pull request #320 from Malabarba/master
Define a variable for locally customizing indentation
2 parents ec312af + 99629de commit 27c2a6f

File tree

2 files changed

+243
-151
lines changed

2 files changed

+243
-151
lines changed

clojure-mode.el

Lines changed: 164 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -638,13 +638,100 @@ point) to check."
638638
(replace-match (clojure-docstring-fill-prefix))))
639639
(lisp-indent-line)))
640640

641-
(defun clojure--symbol-get (function-name property)
642-
"Return the symbol PROPERTY for the symbol named FUNCTION-NAME.
643-
FUNCTION-NAME is a string. If it contains a `/', also try only the part after the `/'."
644-
(or (get (intern-soft function-name) property)
645-
(and (string-match "/\\([^/]+\\)\\'" function-name)
646-
(get (intern-soft (match-string 1 function-name))
647-
property))))
641+
(defvar clojure-get-indent-function nil
642+
"Function to get the indent spec of a symbol.
643+
This function should take one argument, the name of the symbol as
644+
a string. This name will be exactly as it appears in the buffer,
645+
so it might start with a namespace alias.
646+
647+
This function is analogous to the `clojure-indent-function'
648+
symbol property, and its return value should match one of the
649+
allowed values of this property. See `clojure-indent-function'
650+
for more information.")
651+
652+
(defun clojure--get-indent-method (function-name)
653+
"Return the indent spec for the symbol named FUNCTION-NAME.
654+
FUNCTION-NAME is a string. If it contains a `/', also try only
655+
the part after the `/'.
656+
657+
Look for a spec using `clojure-get-indent-function', then try the
658+
`clojure-indent-function' and `clojure-backtracking-indent'
659+
symbol properties."
660+
(or (when (functionp clojure-get-indent-function)
661+
(funcall clojure-get-indent-function function-name))
662+
(get (intern-soft function-name) 'clojure-indent-function)
663+
(get (intern-soft function-name) 'clojure-backtracking-indent)
664+
(when (string-match "/\\([^/]+\\)\\'" function-name)
665+
(or (get (intern-soft (match-string 1 function-name))
666+
'clojure-indent-function)
667+
(get (intern-soft (match-string 1 function-name))
668+
'clojure-backtracking-indent)))))
669+
670+
(defvar clojure--current-backtracking-depth 0)
671+
672+
(defun clojure--find-indent-spec-backtracking ()
673+
"Return the indent sexp that applies to the sexp at point.
674+
Implementation function for `clojure--find-indent-spec'."
675+
(when (and (>= clojure-max-backtracking clojure--current-backtracking-depth)
676+
(not (looking-at "^")))
677+
(let ((clojure--current-backtracking-depth (1+ clojure--current-backtracking-depth))
678+
(pos 0))
679+
;; Count how far we are from the start of the sexp.
680+
(while (ignore-errors (clojure-backward-logical-sexp 1) t)
681+
(cl-incf pos))
682+
(let* ((function (thing-at-point 'symbol))
683+
(method (or (when function ;; Is there a spec here?
684+
(clojure--get-indent-method function))
685+
(progn (up-list) ;; Otherwise look higher up.
686+
(clojure-backward-logical-sexp 1)
687+
(clojure--find-indent-spec-backtracking)))))
688+
(when (numberp method)
689+
(setq method (list method)))
690+
(pcase method
691+
((pred sequencep)
692+
(pcase (length method)
693+
(`0 nil)
694+
(`1 (let ((head (elt method 0)))
695+
(when (or (= pos 0) (sequencep head))
696+
head)))
697+
(l (if (>= pos l)
698+
(elt method (1- l))
699+
(elt method pos)))))
700+
((or `defun `:defn)
701+
(when (= pos 0)
702+
:defn))
703+
(_
704+
(message "Invalid indent spec for `%s': %s" function method)
705+
nil))))))
706+
707+
(defun clojure--find-indent-spec ()
708+
"Return the indent spec that applies to current sexp.
709+
If `clojure-use-backtracking-indent' is non-nil, also do
710+
backtracking up to a higher-level sexp in order to find the
711+
spec."
712+
(if clojure-use-backtracking-indent
713+
(save-excursion
714+
(clojure--find-indent-spec-backtracking))
715+
(let ((function (thing-at-point 'symbol)))
716+
(clojure--get-indent-method function))))
717+
718+
(defun clojure--normal-indent (last-sexp)
719+
"Return the normal indentation column for a sexp.
720+
LAST-SEXP is the start of the previous sexp."
721+
(goto-char last-sexp)
722+
(let ((last-sexp-start nil))
723+
(unless (ignore-errors
724+
(while (progn (skip-chars-backward "#?'`~@[:blank:]")
725+
(not (looking-at "^")))
726+
(setq last-sexp-start (prog1 (point)
727+
(forward-sexp -1))))
728+
t)
729+
;; If the last sexp was on the same line.
730+
(when (and last-sexp-start
731+
(> (line-end-position) last-sexp-start))
732+
(goto-char last-sexp-start)))
733+
(skip-chars-forward "[:blank:]")
734+
(current-column)))
648735

649736
(defun clojure-indent-function (indent-point state)
650737
"When indenting a line within a function call, indent properly.
@@ -666,123 +753,55 @@ The property value can be
666753
- a function to call just as this function was called.
667754
If that function returns nil, that means it doesn't specify
668755
the indentation.
756+
- a list, which is used by `clojure-backtracking-indent'.
669757
670758
This function also returns nil meaning don't specify the indentation."
671-
(let ((normal-indent (current-column)))
672-
(goto-char (1+ (elt state 1)))
673-
(parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
674-
(if (and (elt state 2)
675-
(not (looking-at "\\sw\\|\\s_")))
676-
;; car of form doesn't seem to be a symbol
677-
(progn
678-
(if (not (> (save-excursion (forward-line 1) (point))
679-
calculate-lisp-indent-last-sexp))
680-
(progn (goto-char calculate-lisp-indent-last-sexp)
681-
(skip-chars-backward "[:blank:]")
682-
;; We're done if we find the start of line,
683-
(while (and (not (looking-at-p "^"))
684-
;; or start of sexp.
685-
(ignore-errors (forward-sexp -1) t))
686-
(skip-chars-backward "[:blank:]"))
687-
(parse-partial-sexp (point)
688-
calculate-lisp-indent-last-sexp 0 t)))
689-
;; Indent under the list or under the first sexp on the same
690-
;; line as calculate-lisp-indent-last-sexp. Note that first
691-
;; thing on that line has to be complete sexp since we are
692-
;; inside the innermost containing sexp.
693-
(backward-prefix-chars)
694-
(current-column))
695-
(let* ((function (buffer-substring (point)
696-
(progn (forward-sexp 1) (point))))
697-
(open-paren (elt state 1))
698-
(forward-sexp-function #'clojure-forward-logical-sexp)
699-
(method (clojure--symbol-get function 'clojure-indent-function)))
700-
;; Maps, sets, vectors and reader conditionals.
701-
(cond ((or (member (char-after open-paren) '(?\[ ?\{))
702-
(ignore-errors
703-
(and (eq (char-before open-paren) ?\?)
704-
(eq (char-before (1- open-paren)) ?\#))))
705-
(goto-char open-paren)
706-
(1+ (current-column)))
707-
((or (eq method 'defun)
708-
(and clojure-defun-style-default-indent
709-
;; largely to preserve useful alignment of :require, etc in ns
710-
(not (string-match "^:" function))
711-
(not method))
712-
(and (null method)
713-
(> (length function) 3)
714-
(string-match "\\`\\(?:\\S +/\\)?\\(def\\|with-\\)"
715-
function)))
716-
(lisp-indent-defform state indent-point))
717-
((integerp method)
718-
(lisp-indent-specform method state
719-
indent-point normal-indent))
720-
(method
721-
(funcall method indent-point state))
722-
(clojure-use-backtracking-indent
723-
(clojure-backtracking-indent
724-
indent-point state normal-indent)))))))
725-
726-
(defun clojure-backtracking-indent (indent-point state _normal-indent)
727-
"Experimental backtracking support.
728-
729-
Given an INDENT-POINT, the STATE, and the NORMAL-INDENT, will
730-
move upwards in an sexp to check for contextual indenting."
731-
(let (indent (path) (depth 0))
759+
(let* ((forward-sexp-function #'clojure-forward-logical-sexp))
760+
;; Goto to the open-paren.
732761
(goto-char (elt state 1))
733-
(while (and (not indent)
734-
(< depth clojure-max-backtracking))
735-
(let ((containing-sexp (point)))
736-
(parse-partial-sexp (1+ containing-sexp) indent-point 1 t)
737-
(when (looking-at "\\sw\\|\\s_")
738-
(let* ((start (point))
739-
(fn (buffer-substring start (progn (forward-sexp 1) (point))))
740-
(meth (clojure--symbol-get fn 'clojure-backtracking-indent)))
741-
(let ((n 0))
742-
(when (< (point) indent-point)
743-
(condition-case ()
744-
(progn
745-
(forward-sexp 1)
746-
(while (< (point) indent-point)
747-
(parse-partial-sexp (point) indent-point 1 t)
748-
(cl-incf n)
749-
(forward-sexp 1)))
750-
(error nil)))
751-
(push n path))
752-
(when meth
753-
(let ((def meth))
754-
(dolist (p path)
755-
(if (and (listp def)
756-
(< p (length def)))
757-
(setq def (nth p def))
758-
(if (listp def)
759-
(setq def (car (last def)))
760-
(setq def nil))))
761-
(goto-char (elt state 1))
762-
(when def
763-
(setq indent (+ (current-column) def)))))))
764-
(goto-char containing-sexp)
765-
(condition-case ()
766-
(progn
767-
(backward-up-list 1)
768-
(cl-incf depth))
769-
(error (setq depth clojure-max-backtracking)))))
770-
indent))
771-
772-
;; clojure backtracking indent is experimental and the format for these
773-
;; entries are subject to change
774-
(put 'implement 'clojure-backtracking-indent '(4 (2)))
775-
(put 'letfn 'clojure-backtracking-indent '((2) 2))
776-
(put 'proxy 'clojure-backtracking-indent '(4 4 (2)))
777-
(put 'reify 'clojure-backtracking-indent '((2)))
778-
(put 'deftype 'clojure-backtracking-indent '(4 4 (2)))
779-
(put 'defrecord 'clojure-backtracking-indent '(4 4 (2)))
780-
(put 'defprotocol 'clojure-backtracking-indent '(4 (2)))
781-
(put 'extend-type 'clojure-backtracking-indent '(4 (2)))
782-
(put 'extend-protocol 'clojure-backtracking-indent '(4 (2)))
783-
(put 'specify 'clojure-backtracking-indent '(4 (2)))
784-
(put 'specify! 'clojure-backtracking-indent '(4 (2)))
785-
762+
;; Maps, sets, vectors and reader conditionals.
763+
(if (or (member (char-after) '(?\[ ?\{))
764+
(and (eq (char-before) ?\?)
765+
(eq (char-before (1- (point))) ?\#))
766+
;; Car of form is not a symbol.
767+
(and (elt state 2)
768+
(not (looking-at ".\\sw\\|.\\s_"))))
769+
(1+ (current-column))
770+
;; Function or macro call.
771+
(forward-char 1)
772+
(let ((method (clojure--find-indent-spec))
773+
(containing-form-column (1- (current-column))))
774+
(pcase method
775+
((or (pred integerp) `(,method))
776+
(let ((pos -1))
777+
;; `forward-sexp' will error if indent-point is after
778+
;; the last sexp in the current sexp.
779+
(ignore-errors
780+
(while (<= (point) indent-point)
781+
(clojure-forward-logical-sexp 1)
782+
(cl-incf pos)))
783+
(cond
784+
((= pos (1+ method))
785+
(+ lisp-body-indent containing-form-column))
786+
((> pos (1+ method))
787+
(clojure--normal-indent calculate-lisp-indent-last-sexp))
788+
(t
789+
(+ (* 2 lisp-body-indent) containing-form-column)))))
790+
(`:defn
791+
(+ lisp-body-indent containing-form-column))
792+
((pred functionp)
793+
(funcall method indent-point state))
794+
((and `nil
795+
(guard (let ((function (thing-at-point 'sexp)))
796+
(or (and clojure-defun-style-default-indent
797+
;; largely to preserve useful alignment of :require, etc in ns
798+
(not (string-match "^:" function)))
799+
(string-match "\\`\\(?:\\S +/\\)?\\(def\\|with-\\)"
800+
function)))))
801+
(+ lisp-body-indent containing-form-column))
802+
(_ (clojure--normal-indent calculate-lisp-indent-last-sexp)))))))
803+
804+
;;; Setting indentation
786805
(defun put-clojure-indent (sym indent)
787806
"Instruct `clojure-indent-function' to indent the body of SYM by INDENT."
788807
(put sym 'clojure-indent-function indent))
@@ -808,18 +827,18 @@ Requires the macro's NAME and a VALUE."
808827
809828
You can use this to let Emacs indent your own macros the same way
810829
that it indents built-in macros like with-open. To manually set
811-
it from Lisp code, use (put-clojure-indent 'some-symbol 'defun)."
830+
it from Lisp code, use (put-clojure-indent 'some-symbol :defn)."
812831
:type '(repeat symbol)
813832
:group 'clojure
814833
:set 'add-custom-clojure-indents)
815834

816835
(define-clojure-indent
817836
;; built-ins
818837
(ns 1)
819-
(fn 'defun)
820-
(def 'defun)
821-
(defn 'defun)
822-
(bound-fn 'defun)
838+
(fn :defn)
839+
(def :defn)
840+
(defn :defn)
841+
(bound-fn :defn)
823842
(if 1)
824843
(if-not 1)
825844
(case 1)
@@ -836,26 +855,26 @@ it from Lisp code, use (put-clojure-indent 'some-symbol 'defun)."
836855
(comment 0)
837856
(doto 1)
838857
(locking 1)
839-
(proxy 2)
858+
(proxy '(2 nil nil (1)))
840859
(as-> 2)
841860

842-
(reify 'defun)
843-
(deftype 2)
844-
(defrecord 2)
845-
(defprotocol 1)
861+
(reify '(1 nil (1)))
862+
(deftype '(2 nil nil (1)))
863+
(defrecord '(2 nil nil (1)))
864+
(defprotocol '(1))
846865
(extend 1)
847-
(extend-protocol 1)
848-
(extend-type 1)
849-
(specify 1)
850-
(specify! 1)
851-
866+
(extend-protocol '(1 (1)))
867+
(extend-type '(1 (1)))
868+
(specify '(1 (1)))
869+
(specify! '(1 (1)))
870+
(implement '(1 (1)))
852871
(try 0)
853872
(catch 2)
854873
(finally 0)
855874

856875
;; binding forms
857876
(let 1)
858-
(letfn 1)
877+
(letfn '(1 ((1)) nil))
859878
(binding 1)
860879
(loop 1)
861880
(for 1)
@@ -866,18 +885,18 @@ it from Lisp code, use (put-clojure-indent 'some-symbol 'defun)."
866885
(when-some 1)
867886
(if-some 1)
868887

869-
(defmethod 'defun)
888+
(defmethod :defn)
870889

871890
;; clojure.test
872891
(testing 1)
873-
(deftest 'defun)
892+
(deftest :defn)
874893
(are 2)
875-
(use-fixtures 'defun)
894+
(use-fixtures :defn)
876895

877896
;; core.logic
878-
(run 'defun)
879-
(run* 'defun)
880-
(fresh 'defun)
897+
(run :defn)
898+
(run* :defn)
899+
(fresh :defn)
881900

882901
;; core.async
883902
(alt! 0)
@@ -1089,7 +1108,7 @@ Returns a list pair, e.g. (\"defn\" \"abc\") or (\"deftest\" \"some-test\")."
10891108
Sexps that don't represent code are ^metadata or #reader.macros."
10901109
(forward-sexp 1)
10911110
(forward-sexp -1)
1092-
(not (looking-at-p "\\^\\|#[[:alpha:]]")))
1111+
(not (looking-at-p "\\^\\|#[?[:alpha:]]")))
10931112

10941113
(defun clojure-forward-logical-sexp (&optional n)
10951114
"Move forward N logical sexps.
@@ -1121,7 +1140,7 @@ This will skip over sexps that don't represent objects, so that ^hints and
11211140
(ignore-errors
11221141
(save-excursion
11231142
(backward-sexp 1)
1124-
(looking-at-p "\\^\\|#[[:alpha:]]"))))
1143+
(not (clojure--looking-at-logical-sexp)))))
11251144
(backward-sexp 1))
11261145
(setq n (1- n)))))
11271146

0 commit comments

Comments
 (0)