branch: elpa/clojure-ts-mode
commit 9ae1f42f9648ecac187266bf65a5d8993e6c0765
Author: Roman Rudakov <rruda...@fastmail.com>
Commit: Bozhidar Batsov <bozhi...@batsov.dev>

    Basic support for dynamic indentation
---
 CHANGELOG.md                             |  1 +
 clojure-ts-mode.el                       | 46 ++++++++++++++++++++-----
 test/clojure-ts-mode-indentation-test.el | 59 +++++++++++++++++++++++++++++++-
 3 files changed, 97 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8d7953fd4f..f41749f904 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - Introduce `clojure-ts-semantic-indent-rules` customization option.
 - [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue 
with indentation of collection items with metadata.
 - Proper syntax highlighting for expressions with metadata.
+- Add basic support for dynamic indentation via 
`clojure-ts-get-indent-function`.
 
 ## 0.2.3 (2025-03-04)
 
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
index d0fa310680..e7198b4d85 100644
--- a/clojure-ts-mode.el
+++ b/clojure-ts-mode.el
@@ -871,6 +871,34 @@ indented.)"
     (when-let* ((node-after-bol (treesit-node-first-child-for-pos parent bol)))
       (> (treesit-node-index node-after-bol) (1+ block)))))
 
+(defvar clojure-ts-get-indent-function nil
+  "Function to get the indent spec of a symbol.
+
+This function should take one argument, the name of the symbol as a
+string.  This name will be exactly as it appears in the buffer, so it
+might start with a namespace alias.
+
+The returned value is expected to be the same as
+`clojure-get-indent-function' from `clojure-mode' for compatibility
+reasons.")
+
+(defun clojure-ts--dynamic-indent-for-symbol (symbol-name)
+  "Return dynamic indentation spec for SYMBOL-NAME if found.
+
+If function `clojure-ts-get-indent-function' is not nil, call it and
+produce a valid indentation spec from the returned value.
+
+The indentation rules for `clojure-ts-mode' are simpler than for
+`clojure-mode' so we only take the first integer N and produce `(:block
+N)' rule.  If an integer cannot be found, this function returns nil and
+the default rule is used."
+  (when (functionp clojure-ts-get-indent-function)
+    (let ((spec (funcall clojure-ts-get-indent-function symbol-name)))
+      (if (consp spec)
+          `(:block ,(car spec))
+        (when (integerp spec)
+          `(:block ,spec))))))
+
 (defun clojure-ts--match-form-body (node parent bol)
   "Match if NODE has to be indented as a for body.
 
@@ -879,14 +907,16 @@ indentation rule in 
`clojure-ts--semantic-indent-rules-defaults' or
 `clojure-ts-semantic-indent-rules' check if NODE should be indented
 according to the rule.  If NODE is nil, use next node after BOL."
   (and (clojure-ts--list-node-p parent)
-       (let ((first-child (clojure-ts--node-child-skip-metadata parent 0)))
-         (when-let* ((rule (alist-get (clojure-ts--named-node-text first-child)
-                                      (seq-union 
clojure-ts-semantic-indent-rules
-                                                 
clojure-ts--semantic-indent-rules-defaults
-                                                 (lambda (e1 e2) (equal (car 
e1) (car e2))))
-                                      nil
-                                      nil
-                                      #'equal)))
+       (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))
+              (symbol-name (clojure-ts--named-node-text first-child)))
+         (when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol 
symbol-name)
+                               (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))))
+                                          nil
+                                          nil
+                                          #'equal))))
            (and (not (clojure-ts--match-with-metadata node))
                 (let ((rule-type (car rule))
                       (rule-value (cadr rule)))
diff --git a/test/clojure-ts-mode-indentation-test.el 
b/test/clojure-ts-mode-indentation-test.el
index 8375f802b6..605ec7656a 100644
--- a/test/clojure-ts-mode-indentation-test.el
+++ b/test/clojure-ts-mode-indentation-test.el
@@ -89,6 +89,16 @@ DESCRIPTION is a string with the description of the spec."
     (2 font-lock-function-name-face))))
 
 
+;; Mock `cider--get-symbol-indent' function
+
+(defun cider--get-symbol-indent-mock (symbol-name)
+  "Returns static mocked indentation specs for SYMBOL-NAME if available."
+  (when (stringp symbol-name)
+    (cond
+     ((string-equal symbol-name "my-with-in-str") 1)
+     ((string-equal symbol-name "my-letfn") '(1 ((:defn) (:form)))))))
+
+
 (describe "indentation"
   (it "should not hang on end of buffer"
     (with-clojure-ts-buffer "(let [a b]"
@@ -264,4 +274,51 @@ DESCRIPTION is a string with the description of the spec."
 (let [result ^long
       (if true
         1
-        2)])"))
+        2)])")
+
+(it "should pick up dynamic indentation rules from 
clojure-ts-get-indent-function"
+  (with-clojure-ts-buffer "
+(defmacro my-with-in-str
+  \"[DOCSTRING]\"
+  {:style/indent 1}
+  [s & body]
+  ~@body)
+
+(my-with-in-str \"34\"
+(prompt \"How old are you?\"))"
+    (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
+    (indent-region (point-min) (point-max))
+    (expect (buffer-string) :to-equal "
+(defmacro my-with-in-str
+  \"[DOCSTRING]\"
+  {:style/indent 1}
+  [s & body]
+  ~@body)
+
+(my-with-in-str \"34\"
+  (prompt \"How old are you?\"))"))
+
+  (with-clojure-ts-buffer "
+(defmacro my-letfn
+  \"[DOCSTRING]\"
+  {:style/indent [1 [[:defn]] :form]}
+  [fnspecs & body]
+  ~@body)
+
+(my-letfn [(twice [x] (* x 2))
+           (six-times [y] (* (twice y) 3))]
+(println \"Twice 15 =\" (twice 15))
+(println \"Six times 15 =\" (six-times 15)))"
+    (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock)
+    (indent-region (point-min) (point-max))
+    (expect (buffer-string) :to-equal "
+(defmacro my-letfn
+  \"[DOCSTRING]\"
+  {:style/indent [1 [[:defn]] :form]}
+  [fnspecs & body]
+  ~@body)
+
+(my-letfn [(twice [x] (* x 2))
+           (six-times [y] (* (twice y) 3))]
+  (println \"Twice 15 =\" (twice 15))
+  (println \"Six times 15 =\" (six-times 15)))"))))

Reply via email to