Skip to content

Commit ad3e408

Browse files
committed
Improve performance of clojure-ts-align
Cache semantic indentation rules for a single clojure-ts-align run to avoid lookup for every repeating symbol.
1 parent 5b4fad0 commit ad3e408

3 files changed

Lines changed: 48 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- Fix font-lock issues: duplicate query, missing `definline` metadata docstring.
1212
- Fix missing builtin symbols in font-lock regexp.
1313
- Improve performance for imenu, font-lock and indentation.
14+
- Improve performance for `clojure-ts-align` by caching indentation rules.
1415

1516
## 0.6.0 (2025-12-02)
1617

clojure-ts-mode.el

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,17 +1442,34 @@ If NS is defined, then the fully qualified symbol is passed to
14421442
(seq-sort (lambda (spec1 _spec2)
14431443
(equal (car spec1) :block)))))))))
14441444

1445-
(defun clojure-ts--find-semantic-rules-for-node (node)
1446-
"Return a list of semantic rules for NODE."
1447-
(let* ((first-child (clojure-ts--node-child-skip-metadata node 0))
1448-
(symbol-name (clojure-ts--named-node-text first-child))
1449-
(symbol-namespace (clojure-ts--node-namespace-text first-child)))
1445+
(defvar clojure-ts--dynamic-indent-for-symbol-cache
1446+
(make-hash-table :test 'equal))
1447+
1448+
(defvar clojure-ts--dynamic-indent-for-symbol-cache-p nil
1449+
"If set to nil, do not use cache for dynamic indentation rules.")
1450+
1451+
(defun clojure-ts--find-semantic-rules-for-symbol (node)
1452+
"Return a list of semantic rules for symbol NODE.
1453+
1454+
If rules are not found return :not-found symbol."
1455+
(let ((symbol-name (clojure-ts--named-node-text node))
1456+
(symbol-namespace (clojure-ts--node-namespace-text node)))
14501457
(or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace)
14511458
(alist-get symbol-name
14521459
clojure-ts--semantic-indent-rules-cache
14531460
nil
14541461
nil
1455-
#'equal))))
1462+
#'equal)
1463+
:not-found)))
1464+
1465+
(defun clojure-ts--find-semantic-rules-for-node (node)
1466+
"Return a list of semantic rules for NODE."
1467+
(let* ((first-child (clojure-ts--first-value-child node))
1468+
(symbol-full-name (treesit-node-text first-child)))
1469+
(if clojure-ts--dynamic-indent-for-symbol-cache-p
1470+
(with-memoization (gethash symbol-full-name clojure-ts--dynamic-indent-for-symbol-cache)
1471+
(clojure-ts--find-semantic-rules-for-symbol first-child))
1472+
(clojure-ts--find-semantic-rules-for-symbol first-child))))
14561473

14571474
(defun clojure-ts--find-semantic-rule (node parent current-depth)
14581475
"Return a suitable indentation rule for NODE, considering the CURRENT-DEPTH.
@@ -1464,7 +1481,8 @@ increasing the CURRENT-DEPTH. If a rule is not found upon reaching the
14641481
root of the syntax tree, it returns nil. A rule is considered a match
14651482
only if the CURRENT-DEPTH matches the rule's required depth."
14661483
(let* ((idx (- (treesit-node-index node) 2)))
1467-
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node parent)))
1484+
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node parent))
1485+
((not (equal rule-set :not-found))))
14681486
(if (zerop current-depth)
14691487
(let ((rule (car rule-set)))
14701488
(if (equal (car rule) :block)
@@ -1899,6 +1917,7 @@ subsequent special arguments based on block indentation rules."
18991917
;; indentation rules. First node to skip is the symbol itself.
19001918
(when (equal sexp-type 'cond)
19011919
(if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node node))
1920+
((not (equal rule-set :not-found)))
19021921
(rule (car rule-set))
19031922
((equal (car rule) :block)))
19041923
(treesit-beginning-of-thing 'sexp (1- (- (cadr rule))) 'restrict)
@@ -1919,9 +1938,11 @@ between BEG and END."
19191938
(end (clojure-ts--end-of-defun-pos)))
19201939
(list start end))))))
19211940
(setq end (copy-marker end))
1941+
(clrhash clojure-ts--dynamic-indent-for-symbol-cache)
19221942
(let* ((sexps-to-align (clojure-ts--get-nodes-to-align beg (marker-position end)))
19231943
;; We have to disable it here to avoid endless recursion.
1924-
(clojure-ts-align-forms-automatically nil))
1944+
(clojure-ts-align-forms-automatically nil)
1945+
(clojure-ts--dynamic-indent-for-symbol-cache-p t))
19251946
(save-excursion
19261947
(indent-region beg (marker-position end))
19271948
(dolist (sexp sexps-to-align)

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,24 @@ b |20])"
759759
(it "should remove extra commas"
760760
(with-clojure-ts-buffer-point "{|:a 2, ,:c 4}"
761761
(call-interactively #'clojure-ts-align)
762-
(expect (buffer-string) :to-equal "{:a 2, :c 4}"))))
762+
(expect (buffer-string) :to-equal "{:a 2, :c 4}")))
763+
764+
(it "should not fail to align a cond form without a semantic indentation rule"
765+
;; A symbol added to `clojure-ts-align-cond-forms' need not have a matching
766+
;; semantic indentation rule. When it has none, alignment must fall back
767+
;; gracefully instead of raising `(wrong-type-argument listp :not-found)'.
768+
(with-clojure-ts-buffer-point "
769+
(my-cond
770+
|:a 1
771+
:bbbb 2
772+
:cc 3)"
773+
(setq-local clojure-ts-align-cond-forms (cons "my-cond" clojure-ts-align-cond-forms))
774+
(call-interactively #'clojure-ts-align)
775+
(expect (buffer-string) :to-equal "
776+
(my-cond
777+
:a 1
778+
:bbbb 2
779+
:cc 3)"))))
763780

764781
(describe "clojure-ts-align-forms-automatically"
765782
;; Copied from `clojure-mode'

0 commit comments

Comments
 (0)