Skip to content

Commit b57f024

Browse files
committed
Improve performance of semantic indentation by caching rules
1 parent 9542792 commit b57f024

File tree

3 files changed

+89
-24
lines changed

3 files changed

+89
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules.
1919
- [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form.
2020
- [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`.
21+
- Improve performance of semantic indentation by caching rules.
2122

2223
## 0.2.3 (2025-03-04)
2324

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,24 @@ For example:
210210
- `defn` and `fn` have a rule `((:inner 0))`.
211211
- `letfn` has a rule `((:block 1) (:inner 2 0))`.
212212

213+
Note that `clojure-ts-semantic-indent-rules` should be set using the
214+
customization interface or `setopt`; otherwise, it will not be applied
215+
correctly.
216+
217+
#### Project local indentation
218+
219+
Custom indentation rules can be set for individual projects. To achieve this,
220+
you need to create a `.dir-locals.el` file in the project root. The content
221+
should look like:
222+
223+
```emacs-lisp
224+
((clojure-ts-mode . ((clojure-ts-semantic-indent-rules . (("with-transaction" . ((:block 1)))
225+
("with-retry" . ((:block 1))))))))
226+
```
227+
228+
In order to apply directory-local variables to existing buffers, they must be
229+
reverted.
230+
213231
### Font Locking
214232

215233
To highlight entire rich `comment` expression with the comment font face, set

clojure-ts-mode.el

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,26 +125,6 @@ double quotes on the third column."
125125
:type 'boolean
126126
:package-version '(clojure-ts-mode . "0.3"))
127127

128-
(defcustom clojure-ts-semantic-indent-rules nil
129-
"Custom rules to extend default indentation rules for `semantic' style.
130-
131-
Each rule is an alist entry which looks like `(\"symbol-name\"
132-
. (rule-type rule-value))', where rule-type is one either `:block' or
133-
`:inner' and rule-value is an integer. The semantic is similar to
134-
cljfmt indentation rules.
135-
136-
Default set of rules is defined in
137-
`clojure-ts--semantic-indent-rules-defaults'."
138-
:safe #'listp
139-
:type '(alist :key-type string
140-
:value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block)
141-
(const :tag "Inner indentation rule" :inner))
142-
integer)
143-
(list (const :tag "Inner indentation rule" :inner)
144-
integer
145-
integer))))
146-
:package-version '(clojure-ts-mode . "0.3"))
147-
148128
(defvar clojure-ts-mode-remappings
149129
'((clojure-mode . clojure-ts-mode)
150130
(clojurescript-mode . clojure-ts-clojurescript-mode)
@@ -864,6 +844,61 @@ The format reflects cljfmt indentation rules. All the default rules are
864844
aligned with
865845
https://github.com/weavejester/cljfmt/blob/0.13.0/cljfmt/resources/cljfmt/indents/clojure.clj")
866846

847+
(defvar-local clojure-ts--semantic-indent-rules-cache nil)
848+
849+
(defun clojure-ts--compute-semantic-indentation-rules-cache (rules)
850+
"Compute the combined semantic indentation rules cache.
851+
852+
If RULES are not provided, this function computes the union of
853+
`clojure-ts-semantic-indent-rules' and
854+
`clojure-ts--semantic-indent-rules-defaults', prioritizing user-defined
855+
rules. If RULES are provided, this function uses them instead of
856+
`clojure-ts-semantic-indent-rules'.
857+
858+
This function is called when the `clojure-ts-semantic-indent-rules'
859+
variable is customized using setopt or the Emacs customization
860+
interface. It is also called when file-local variables are updated.
861+
This ensures that updated indentation rules are always precalculated."
862+
(seq-union rules
863+
clojure-ts--semantic-indent-rules-defaults
864+
(lambda (e1 e2) (equal (car e1) (car e2)))))
865+
866+
(defun clojure-ts--set-semantic-indent-rules (symbol value)
867+
"Setter function for `clojure-ts-semantic-indent-rules' variable.
868+
869+
Sets SYMBOL's top-level default value to VALUE and updates the
870+
`clojure-ts--semantic-indent-rules-cache' in all `clojure-ts-mode'
871+
buffers, if any exist.
872+
873+
NOTE: This function is not meant to be called directly."
874+
(set-default-toplevel-value symbol value)
875+
;; Update cache in every `clojure-ts-mode' buffer.
876+
(let ((new-cache (clojure-ts--compute-semantic-indentation-rules-cache value)))
877+
(dolist (buf (buffer-list))
878+
(when (buffer-local-boundp 'clojure-ts--semantic-indent-rules-cache buf)
879+
(setq clojure-ts--semantic-indent-rules-cache new-cache)))))
880+
881+
(defcustom clojure-ts-semantic-indent-rules nil
882+
"Custom rules to extend default indentation rules for `semantic' style.
883+
884+
Each rule is an alist entry which looks like `(\"symbol-name\"
885+
. (rule-type rule-value))', where rule-type is one either `:block' or
886+
`:inner' and rule-value is an integer. The semantic is similar to
887+
cljfmt indentation rules.
888+
889+
Default set of rules is defined in
890+
`clojure-ts--semantic-indent-rules-defaults'."
891+
:safe #'listp
892+
:type '(alist :key-type string
893+
:value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block)
894+
(const :tag "Inner indentation rule" :inner))
895+
integer)
896+
(list (const :tag "Inner indentation rule" :inner)
897+
integer
898+
integer))))
899+
:package-version '(clojure-ts-mode . "0.3")
900+
:set #'clojure-ts--set-semantic-indent-rules)
901+
867902
(defun clojure-ts--match-block-0-body (bol first-child)
868903
"Match if expression body is not at the same line as FIRST-CHILD.
869904
@@ -929,7 +964,7 @@ For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2)).
929964
930965
If NS is defined, then the fully qualified symbol is passed to
931966
`clojure-ts-get-indent-function'."
932-
(when (functionp clojure-ts-get-indent-function)
967+
(when (and sym (functionp clojure-ts-get-indent-function))
933968
(let* ((full-symbol (if ns
934969
(concat ns "/" sym)
935970
sym))
@@ -964,9 +999,7 @@ only if the CURRENT-DEPTH matches the rule's required depth."
964999
(idx (- (treesit-node-index node) 2)))
9651000
(if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace)
9661001
(alist-get symbol-name
967-
(seq-union clojure-ts-semantic-indent-rules
968-
clojure-ts--semantic-indent-rules-defaults
969-
(lambda (e1 e2) (equal (car e1) (car e2))))
1002+
clojure-ts--semantic-indent-rules-cache
9701003
nil
9711004
nil
9721005
#'equal))))
@@ -1311,6 +1344,19 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE."
13111344

13121345
(treesit-major-mode-setup)
13131346

1347+
;; Initial indentation rules cache calculation.
1348+
(setq clojure-ts--semantic-indent-rules-cache
1349+
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))
1350+
1351+
;; If indentation rules are set in `.dir-locals.el', it is advisable to
1352+
;; recalculate the buffer-local value whenever the value changes.
1353+
(add-hook 'hack-local-variables-hook
1354+
(lambda ()
1355+
(setq clojure-ts--semantic-indent-rules-cache
1356+
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules)))
1357+
0
1358+
t)
1359+
13141360
;; Workaround for treesit-transpose-sexps not correctly working with
13151361
;; treesit-thing-settings on Emacs 30.
13161362
;; Once treesit-transpose-sexps it working again this can be removed

0 commit comments

Comments
 (0)