Skip to content
Open
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 @@ -8,6 +8,7 @@
- Fix font-lock issues: duplicate query, missing `definline` metadata docstring.
- Fix missing builtin symbols in font-lock regexp.
- Improve performance for imenu, font-lock and indentation.
- Improve performance for `clojure-ts-align` by caching indentation rules.

## 0.6.0 (2025-12-02)

Expand Down
37 changes: 29 additions & 8 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -1442,17 +1442,34 @@ If NS is defined, then the fully qualified symbol is passed to
(seq-sort (lambda (spec1 _spec2)
(equal (car spec1) :block)))))))))

(defun clojure-ts--find-semantic-rules-for-node (node)
"Return a list of semantic rules for NODE."
(let* ((first-child (clojure-ts--node-child-skip-metadata node 0))
(symbol-name (clojure-ts--named-node-text first-child))
(symbol-namespace (clojure-ts--node-namespace-text first-child)))
(defvar clojure-ts--dynamic-indent-for-symbol-cache
(make-hash-table :test 'equal))

(defvar clojure-ts--dynamic-indent-for-symbol-cache-p nil
"If set to nil, do not use cache for dynamic indentation rules.")

(defun clojure-ts--find-semantic-rules-for-symbol (node)
"Return a list of semantic rules for symbol NODE.

If rules are not found return :not-found symbol."
(let ((symbol-name (clojure-ts--named-node-text node))
(symbol-namespace (clojure-ts--node-namespace-text node)))
(or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace)
(alist-get symbol-name
clojure-ts--semantic-indent-rules-cache
nil
nil
#'equal))))
#'equal)
:not-found)))

(defun clojure-ts--find-semantic-rules-for-node (node)
"Return a list of semantic rules for NODE."
(let* ((first-child (clojure-ts--first-value-child node))
(symbol-full-name (treesit-node-text first-child)))
(if clojure-ts--dynamic-indent-for-symbol-cache-p
(with-memoization (gethash symbol-full-name clojure-ts--dynamic-indent-for-symbol-cache)
(clojure-ts--find-semantic-rules-for-symbol first-child))
(clojure-ts--find-semantic-rules-for-symbol first-child))))

(defun clojure-ts--find-semantic-rule (node parent current-depth)
"Return a suitable indentation rule for NODE, considering the CURRENT-DEPTH.
Expand All @@ -1464,7 +1481,8 @@ increasing the CURRENT-DEPTH. If a rule is not found upon reaching the
root of the syntax tree, it returns nil. A rule is considered a match
only if the CURRENT-DEPTH matches the rule's required depth."
(let* ((idx (- (treesit-node-index node) 2)))
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node parent)))
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node parent))
((not (equal rule-set :not-found))))
(if (zerop current-depth)
(let ((rule (car rule-set)))
(if (equal (car rule) :block)
Expand Down Expand Up @@ -1899,6 +1917,7 @@ subsequent special arguments based on block indentation rules."
;; indentation rules. First node to skip is the symbol itself.
(when (equal sexp-type 'cond)
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node node))
((not (equal rule-set :not-found)))
(rule (car rule-set))
((equal (car rule) :block)))
(treesit-beginning-of-thing 'sexp (1- (- (cadr rule))) 'restrict)
Expand All @@ -1919,9 +1938,11 @@ between BEG and END."
(end (clojure-ts--end-of-defun-pos)))
(list start end))))))
(setq end (copy-marker end))
(clrhash clojure-ts--dynamic-indent-for-symbol-cache)
(let* ((sexps-to-align (clojure-ts--get-nodes-to-align beg (marker-position end)))
;; We have to disable it here to avoid endless recursion.
(clojure-ts-align-forms-automatically nil))
(clojure-ts-align-forms-automatically nil)
(clojure-ts--dynamic-indent-for-symbol-cache-p t))
(save-excursion
(indent-region beg (marker-position end))
(dolist (sexp sexps-to-align)
Expand Down
53 changes: 35 additions & 18 deletions test/clojure-ts-mode-indentation-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@ represents the expected position of point.
DESCRIPTION is a string with the description of the spec."
(declare (indent 1))
`(it ,description
(let* ((after ,after)
(expected-cursor-pos (1+ (clojure-ts--s-index-of "|" after)))
(expected-state (delete ?| after)))
(with-clojure-ts-buffer ,before
(goto-char (point-min))
(search-forward "|")
(delete-char -1)
(font-lock-ensure)
(indent-according-to-mode)
(expect (buffer-string) :to-equal expected-state)
(expect (point) :to-equal expected-cursor-pos)))))
(let* ((after ,after)
(expected-cursor-pos (1+ (clojure-ts--s-index-of "|" after)))
(expected-state (delete ?| after)))
(with-clojure-ts-buffer ,before
(goto-char (point-min))
(search-forward "|")
(delete-char -1)
(font-lock-ensure)
(indent-according-to-mode)
(expect (buffer-string) :to-equal expected-state)
(expect (point) :to-equal expected-cursor-pos)))))



Expand Down Expand Up @@ -698,7 +698,7 @@ DESCRIPTION is a string with the description of the spec."
(with-clojure-ts-buffer-point "
(let [a-long-name 10
b |20])"
(call-interactively #'clojure-ts-align)
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
(let [a-long-name 10
b 20])"))
Expand All @@ -713,7 +713,7 @@ b |20])"
:a #long \"1234\"
:b {:this \"is\"
:nested \"map\"}}])"
(call-interactively #'clojure-ts-align)
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
(let [^long my-map {:hello \"World\" ;Hello
:foo
Expand All @@ -730,7 +730,7 @@ b |20])"
|123 \"Hello\"
99999 \"World\"
234 nil)"
(call-interactively #'clojure-ts-align)
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
(condp = 2
123 \"Hello\"
Expand All @@ -741,7 +741,7 @@ b |20])"
(with-clojure-ts-buffer-point "
#?(:clj 2
|:cljs 2)"
(call-interactively #'clojure-ts-align)
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
#?(:clj 2
:cljs 2)")))
Expand All @@ -750,16 +750,33 @@ b |20])"
(with-clojure-ts-buffer-point "
#?(:clj 2
|:cljs 2)"
(setq-local clojure-ts-align-reader-conditionals t)
(setq-local clojure-ts-align-reader-conditionals t)
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
#?(:clj 2
:cljs 2)")))

(it "should remove extra commas"
(with-clojure-ts-buffer-point "{|:a 2, ,:c 4}"
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "{:a 2, :c 4}"))))
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "{:a 2, :c 4}")))

(it "should not fail to align a cond form without a semantic indentation rule"
;; A symbol added to `clojure-ts-align-cond-forms' need not have a matching
;; semantic indentation rule. When it has none, alignment must fall back
;; gracefully instead of raising `(wrong-type-argument listp :not-found)'.
(with-clojure-ts-buffer-point "
(my-cond
|:a 1
:bbbb 2
:cc 3)"
(setq-local clojure-ts-align-cond-forms (cons "my-cond" clojure-ts-align-cond-forms))
(call-interactively #'clojure-ts-align)
(expect (buffer-string) :to-equal "
(my-cond
:a 1
:bbbb 2
:cc 3)"))))

(describe "clojure-ts-align-forms-automatically"
;; Copied from `clojure-mode'
Expand Down
Loading