Skip to content

Improve performance of semantic indentation by caching rules #76

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
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 @@ -18,6 +18,7 @@
- [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules.
- [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form.
- [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`.
- Improve performance of semantic indentation by caching rules.

## 0.2.3 (2025-03-04)

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,24 @@ For example:
- `defn` and `fn` have a rule `((:inner 0))`.
- `letfn` has a rule `((:block 1) (:inner 2 0))`.

Note that `clojure-ts-semantic-indent-rules` should be set using the
customization interface or `setopt`; otherwise, it will not be applied
correctly.

#### Project local indentation

Custom indentation rules can be set for individual projects. To achieve this,
you need to create a `.dir-locals.el` file in the project root. The content
should look like:

```emacs-lisp
((clojure-ts-mode . ((clojure-ts-semantic-indent-rules . (("with-transaction" . ((:block 1)))
("with-retry" . ((:block 1))))))))
```

In order to apply directory-local variables to existing buffers, they must be
reverted.

### Font Locking

To highlight entire rich `comment` expression with the comment font face, set
Expand Down
94 changes: 70 additions & 24 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,6 @@ double quotes on the third column."
:type 'boolean
:package-version '(clojure-ts-mode . "0.3"))

(defcustom clojure-ts-semantic-indent-rules nil
"Custom rules to extend default indentation rules for `semantic' style.

Each rule is an alist entry which looks like `(\"symbol-name\"
. (rule-type rule-value))', where rule-type is one either `:block' or
`:inner' and rule-value is an integer. The semantic is similar to
cljfmt indentation rules.

Default set of rules is defined in
`clojure-ts--semantic-indent-rules-defaults'."
:safe #'listp
:type '(alist :key-type string
:value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block)
(const :tag "Inner indentation rule" :inner))
integer)
(list (const :tag "Inner indentation rule" :inner)
integer
integer))))
:package-version '(clojure-ts-mode . "0.3"))

(defvar clojure-ts-mode-remappings
'((clojure-mode . clojure-ts-mode)
(clojurescript-mode . clojure-ts-clojurescript-mode)
Expand Down Expand Up @@ -864,6 +844,61 @@ The format reflects cljfmt indentation rules. All the default rules are
aligned with
https://github.com/weavejester/cljfmt/blob/0.13.0/cljfmt/resources/cljfmt/indents/clojure.clj")

(defvar-local clojure-ts--semantic-indent-rules-cache nil)

(defun clojure-ts--compute-semantic-indentation-rules-cache (rules)
"Compute the combined semantic indentation rules cache.

If RULES are not provided, this function computes the union of
`clojure-ts-semantic-indent-rules' and
`clojure-ts--semantic-indent-rules-defaults', prioritizing user-defined
rules. If RULES are provided, this function uses them instead of
`clojure-ts-semantic-indent-rules'.

This function is called when the `clojure-ts-semantic-indent-rules'
variable is customized using setopt or the Emacs customization
interface. It is also called when file-local variables are updated.
This ensures that updated indentation rules are always precalculated."
(seq-union rules
clojure-ts--semantic-indent-rules-defaults
(lambda (e1 e2) (equal (car e1) (car e2)))))

(defun clojure-ts--set-semantic-indent-rules (symbol value)
"Setter function for `clojure-ts-semantic-indent-rules' variable.

Sets SYMBOL's top-level default value to VALUE and updates the
`clojure-ts--semantic-indent-rules-cache' in all `clojure-ts-mode'
buffers, if any exist.

NOTE: This function is not meant to be called directly."
(set-default-toplevel-value symbol value)
;; Update cache in every `clojure-ts-mode' buffer.
(let ((new-cache (clojure-ts--compute-semantic-indentation-rules-cache value)))
(dolist (buf (buffer-list))
(when (buffer-local-boundp 'clojure-ts--semantic-indent-rules-cache buf)
(setq clojure-ts--semantic-indent-rules-cache new-cache)))))

(defcustom clojure-ts-semantic-indent-rules nil
"Custom rules to extend default indentation rules for `semantic' style.

Each rule is an alist entry which looks like `(\"symbol-name\"
. (rule-type rule-value))', where rule-type is one either `:block' or
`:inner' and rule-value is an integer. The semantic is similar to
cljfmt indentation rules.

Default set of rules is defined in
`clojure-ts--semantic-indent-rules-defaults'."
:safe #'listp
:type '(alist :key-type string
:value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block)
(const :tag "Inner indentation rule" :inner))
integer)
(list (const :tag "Inner indentation rule" :inner)
integer
integer))))
:package-version '(clojure-ts-mode . "0.3")
:set #'clojure-ts--set-semantic-indent-rules)

(defun clojure-ts--match-block-0-body (bol first-child)
"Match if expression body is not at the same line as FIRST-CHILD.

Expand Down Expand Up @@ -929,7 +964,7 @@ For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2)).

If NS is defined, then the fully qualified symbol is passed to
`clojure-ts-get-indent-function'."
(when (functionp clojure-ts-get-indent-function)
(when (and sym (functionp clojure-ts-get-indent-function))
(let* ((full-symbol (if ns
(concat ns "/" sym)
sym))
Expand Down Expand Up @@ -964,9 +999,7 @@ only if the CURRENT-DEPTH matches the rule's required depth."
(idx (- (treesit-node-index node) 2)))
(if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace)
(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))))
clojure-ts--semantic-indent-rules-cache
nil
nil
#'equal))))
Expand Down Expand Up @@ -1311,6 +1344,19 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE."

(treesit-major-mode-setup)

;; Initial indentation rules cache calculation.
(setq clojure-ts--semantic-indent-rules-cache
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))

;; If indentation rules are set in `.dir-locals.el', it is advisable to
;; recalculate the buffer-local value whenever the value changes.
(add-hook 'hack-local-variables-hook
(lambda ()
(setq clojure-ts--semantic-indent-rules-cache
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules)))
0
t)

;; Workaround for treesit-transpose-sexps not correctly working with
;; treesit-thing-settings on Emacs 30.
;; Once treesit-transpose-sexps it working again this can be removed
Expand Down
Loading