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)))"))))