Skip to content

Basic support for dynamic indentation #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Introduce `clojure-ts-semantic-indent-rules` customization option.
- [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata.
- Proper syntax highlighting for expressions with metadata.
- Add basic support for dynamic indentation via `clojure-ts-get-indent-function`.

## 0.2.3 (2025-03-04)

Expand Down
46 changes: 38 additions & 8 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,34 @@ indented.)"
(when-let* ((node-after-bol (treesit-node-first-child-for-pos parent bol)))
(> (treesit-node-index node-after-bol) (1+ block)))))

(defvar clojure-ts-get-indent-function nil
"Function to get the indent spec of a symbol.

This function should take one argument, the name of the symbol as a
string. This name will be exactly as it appears in the buffer, so it
might start with a namespace alias.

The returned value is expected to be the same as
`clojure-get-indent-function' from `clojure-mode' for compatibility
reasons.")

(defun clojure-ts--dynamic-indent-for-symbol (symbol-name)
"Return dynamic indentation spec for SYMBOL-NAME if found.

If function `clojure-ts-get-indent-function' is not nil, call it and
produce a valid indentation spec from the returned value.

The indentation rules for `clojure-ts-mode' are simpler than for
`clojure-mode' so we only take the first integer N and produce `(:block
N)' rule. If an integer cannot be found, this function returns nil and
the default rule is used."
(when (functionp clojure-ts-get-indent-function)
(let ((spec (funcall clojure-ts-get-indent-function symbol-name)))
(if (consp spec)
`(:block ,(car spec))
(when (integerp spec)
`(:block ,spec))))))

(defun clojure-ts--match-form-body (node parent bol)
"Match if NODE has to be indented as a for body.

Expand All @@ -879,14 +907,16 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or
`clojure-ts-semantic-indent-rules' check if NODE should be indented
according to the rule. If NODE is nil, use next node after BOL."
(and (clojure-ts--list-node-p parent)
(let ((first-child (clojure-ts--node-child-skip-metadata parent 0)))
(when-let* ((rule (alist-get (clojure-ts--named-node-text first-child)
(seq-union clojure-ts-semantic-indent-rules
clojure-ts--semantic-indent-rules-defaults
(lambda (e1 e2) (equal (car e1) (car e2))))
nil
nil
#'equal)))
(let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))
(symbol-name (clojure-ts--named-node-text first-child)))
(when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol symbol-name)
(alist-get symbol-name
(seq-union clojure-ts-semantic-indent-rules
clojure-ts--semantic-indent-rules-defaults
(lambda (e1 e2) (equal (car e1) (car e2))))
nil
nil
#'equal))))
(and (not (clojure-ts--match-with-metadata node))
(let ((rule-type (car rule))
(rule-value (cadr rule)))
Expand Down
59 changes: 58 additions & 1 deletion test/clojure-ts-mode-indentation-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ DESCRIPTION is a string with the description of the spec."
(2 font-lock-function-name-face))))


;; Mock `cider--get-symbol-indent' function

(defun cider--get-symbol-indent-mock (symbol-name)
"Returns static mocked indentation specs for SYMBOL-NAME if available."
(when (stringp symbol-name)
(cond
((string-equal symbol-name "my-with-in-str") 1)
((string-equal symbol-name "my-letfn") '(1 ((:defn) (:form)))))))


(describe "indentation"
(it "should not hang on end of buffer"
(with-clojure-ts-buffer "(let [a b]"
Expand Down Expand Up @@ -264,4 +274,51 @@ DESCRIPTION is a string with the description of the spec."
(let [result ^long
(if true
1
2)])"))
2)])")

(it "should pick up dynamic indentation rules from clojure-ts-get-indent-function"
(with-clojure-ts-buffer "
(defmacro my-with-in-str
\"[DOCSTRING]\"
{:style/indent 1}
[s & body]
~@body)

(my-with-in-str \"34\"
(prompt \"How old are you?\"))"
(setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
(indent-region (point-min) (point-max))
(expect (buffer-string) :to-equal "
(defmacro my-with-in-str
\"[DOCSTRING]\"
{:style/indent 1}
[s & body]
~@body)

(my-with-in-str \"34\"
(prompt \"How old are you?\"))"))

(with-clojure-ts-buffer "
(defmacro my-letfn
\"[DOCSTRING]\"
{:style/indent [1 [[:defn]] :form]}
[fnspecs & body]
~@body)

(my-letfn [(twice [x] (* x 2))
(six-times [y] (* (twice y) 3))]
(println \"Twice 15 =\" (twice 15))
(println \"Six times 15 =\" (six-times 15)))"
(setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
(indent-region (point-min) (point-max))
(expect (buffer-string) :to-equal "
(defmacro my-letfn
\"[DOCSTRING]\"
{:style/indent [1 [[:defn]] :form]}
[fnspecs & body]
~@body)

(my-letfn [(twice [x] (* x 2))
(six-times [y] (* (twice y) 3))]
(println \"Twice 15 =\" (twice 15))
(println \"Six times 15 =\" (six-times 15)))"))))
Loading