diff --git a/CHANGELOG.md b/CHANGELOG.md index 2135331..5f4517e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/README.md b/README.md index cad93f3..c9374ac 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a8450bc..cb71cb9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -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) @@ -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. @@ -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)) @@ -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)))) @@ -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