Skip to content

Commit 5085667

Browse files
committed
Basic support for dynamic indentation
1 parent 7497ae9 commit 5085667

File tree

3 files changed

+97
-9
lines changed

3 files changed

+97
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Introduce `clojure-ts-semantic-indent-rules` customization option.
1515
- [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata.
1616
- Proper syntax highlighting for expressions with metadata.
17+
- Add basic support for dynamic indentation via `clojure-ts-get-indent-function`.
1718

1819
## 0.2.3 (2025-03-04)
1920

clojure-ts-mode.el

+38-8
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,34 @@ indented.)"
871871
(when-let* ((node-after-bol (treesit-node-first-child-for-pos parent bol)))
872872
(> (treesit-node-index node-after-bol) (1+ block)))))
873873

874+
(defvar clojure-ts-get-indent-function nil
875+
"Function to get the indent spec of a symbol.
876+
877+
This function should take one argument, the name of the symbol as a
878+
string. This name will be exactly as it appears in the buffer, so it
879+
might start with a namespace alias.
880+
881+
The returned value is expected to be the same as
882+
`clojure-get-indent-function' from `clojure-mode' for compatibility
883+
reasons.")
884+
885+
(defun clojure-ts--dynamic-indent-for-symbol (symbol-name)
886+
"Return dynamic indentation spec for SYMBOL-NAME if found.
887+
888+
If function `clojure-ts-get-indent-function' is not nil, call it and
889+
produce a valid indentation spec from the returned value.
890+
891+
The indentation rules for `clojure-ts-mode' are simpler than for
892+
`clojure-mode' so we only take the first integer N and produce `(:block
893+
N)' rule. If an integer cannot be found, this function returns nil and
894+
the default rule is used."
895+
(when (functionp clojure-ts-get-indent-function)
896+
(let ((spec (funcall clojure-ts-get-indent-function symbol-name)))
897+
(if (consp spec)
898+
`(:block ,(car spec))
899+
(when (integerp spec)
900+
`(:block ,spec))))))
901+
874902
(defun clojure-ts--match-form-body (node parent bol)
875903
"Match if NODE has to be indented as a for body.
876904
@@ -879,14 +907,16 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or
879907
`clojure-ts-semantic-indent-rules' check if NODE should be indented
880908
according to the rule. If NODE is nil, use next node after BOL."
881909
(and (clojure-ts--list-node-p parent)
882-
(let ((first-child (clojure-ts--node-child-skip-metadata parent 0)))
883-
(when-let* ((rule (alist-get (clojure-ts--named-node-text first-child)
884-
(seq-union clojure-ts-semantic-indent-rules
885-
clojure-ts--semantic-indent-rules-defaults
886-
(lambda (e1 e2) (equal (car e1) (car e2))))
887-
nil
888-
nil
889-
#'equal)))
910+
(let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))
911+
(symbol-name (clojure-ts--named-node-text first-child)))
912+
(when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol symbol-name)
913+
(alist-get symbol-name
914+
(seq-union clojure-ts-semantic-indent-rules
915+
clojure-ts--semantic-indent-rules-defaults
916+
(lambda (e1 e2) (equal (car e1) (car e2))))
917+
nil
918+
nil
919+
#'equal))))
890920
(and (not (clojure-ts--match-with-metadata node))
891921
(let ((rule-type (car rule))
892922
(rule-value (cadr rule)))

test/clojure-ts-mode-indentation-test.el

+58-1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ DESCRIPTION is a string with the description of the spec."
8989
(2 font-lock-function-name-face))))
9090

9191

92+
;; Mock `cider--get-symbol-indent' function
93+
94+
(defun cider--get-symbol-indent-mock (symbol-name)
95+
"Returns static mocked indentation specs for SYMBOL-NAME if available."
96+
(when (stringp symbol-name)
97+
(cond
98+
((string-equal symbol-name "my-with-in-str") 1)
99+
((string-equal symbol-name "my-letfn") '(1 ((:defn) (:form)))))))
100+
101+
92102
(describe "indentation"
93103
(it "should not hang on end of buffer"
94104
(with-clojure-ts-buffer "(let [a b]"
@@ -264,4 +274,51 @@ DESCRIPTION is a string with the description of the spec."
264274
(let [result ^long
265275
(if true
266276
1
267-
2)])"))
277+
2)])")
278+
279+
(it "should pick up dynamic indentation rules from clojure-ts-get-indent-function"
280+
(with-clojure-ts-buffer "
281+
(defmacro my-with-in-str
282+
\"[DOCSTRING]\"
283+
{:style/indent 1}
284+
[s & body]
285+
~@body)
286+
287+
(my-with-in-str \"34\"
288+
(prompt \"How old are you?\"))"
289+
(setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
290+
(indent-region (point-min) (point-max))
291+
(expect (buffer-string) :to-equal "
292+
(defmacro my-with-in-str
293+
\"[DOCSTRING]\"
294+
{:style/indent 1}
295+
[s & body]
296+
~@body)
297+
298+
(my-with-in-str \"34\"
299+
(prompt \"How old are you?\"))"))
300+
301+
(with-clojure-ts-buffer "
302+
(defmacro my-letfn
303+
\"[DOCSTRING]\"
304+
{:style/indent [1 [[:defn]] :form]}
305+
[fnspecs & body]
306+
~@body)
307+
308+
(my-letfn [(twice [x] (* x 2))
309+
(six-times [y] (* (twice y) 3))]
310+
(println \"Twice 15 =\" (twice 15))
311+
(println \"Six times 15 =\" (six-times 15)))"
312+
(setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
313+
(indent-region (point-min) (point-max))
314+
(expect (buffer-string) :to-equal "
315+
(defmacro my-letfn
316+
\"[DOCSTRING]\"
317+
{:style/indent [1 [[:defn]] :form]}
318+
[fnspecs & body]
319+
~@body)
320+
321+
(my-letfn [(twice [x] (* x 2))
322+
(six-times [y] (* (twice y) 3))]
323+
(println \"Twice 15 =\" (twice 15))
324+
(println \"Six times 15 =\" (six-times 15)))"))))

0 commit comments

Comments
 (0)