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

    Fix some issues with short anonymous functions
---
 CHANGELOG.md                             |  4 +++
 clojure-ts-mode.el                       | 58 ++++++++++++++++++++++++++------
 test/clojure-ts-mode-font-lock-test.el   |  4 +++
 test/clojure-ts-mode-indentation-test.el | 17 ++++++++++
 test/samples/test.clj                    | 13 +++++++
 5 files changed, 85 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cecf8a2f24..d40be97361 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
 - [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable 
regex syntax highlighting.
 - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Add 
support for automatic aligning forms.
 - [#82](https://github.com/clojure-emacs/clojure-ts-mode/issues/82): Introduce 
`clojure-ts-outline-variant`.
+- [#86](https://github.com/clojure-emacs/clojure-ts-mode/pull/86): Better 
handling of function literals:
+  - Syntax highlighting of built-in keywords.
+  - Consistent indentation with regular forms.
+  - Support for automatic aligning forms.
 
 ## 0.3.0 (2025-04-15)
 
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
index 51c7996a99..340e01625a 100644
--- a/clojure-ts-mode.el
+++ b/clojure-ts-mode.el
@@ -514,6 +514,13 @@ literals with regex grammar."
                                       (:equal "clojure.core" @ns))
                           name: (sym_name) @font-lock-keyword-face))
        (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face))
+      ((anon_fn_lit meta: _ :* :anchor (sym_lit !namespace name: (sym_name) 
@font-lock-keyword-face))
+       (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face))
+      ((anon_fn_lit meta: _ :* :anchor
+                    (sym_lit namespace: ((sym_ns) @ns
+                                         (:equal "clojure.core" @ns))
+                             name: (sym_name) @font-lock-keyword-face))
+       (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face))
       ((sym_name) @font-lock-builtin-face
        (:match ,clojure-ts--builtin-dynamic-var-regexp 
@font-lock-builtin-face)))
 
@@ -726,6 +733,14 @@ literals with regex grammar."
   "Return non-nil if NODE is a Clojure list."
   (string-equal "list_lit" (treesit-node-type node)))
 
+(defun clojure-ts--anon-fn-node-p (node)
+  "Return non-nil if NODE is a Clojure function literal."
+  (string-equal "anon_fn_lit" (treesit-node-type node)))
+
+(defun clojure-ts--opening-paren-node-p (node)
+  "Return non-nil if NODE is an opening paren."
+  (string-equal "(" (treesit-node-text node)))
+
 (defun clojure-ts--symbol-node-p (node)
   "Return non-nil if NODE is a Clojure symbol."
   (string-equal "sym_lit" (treesit-node-type node)))
@@ -1249,7 +1264,8 @@ PARENT not should be a list.  If first symbol in the 
expression has an
 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)
+  (and (or (clojure-ts--list-node-p parent)
+           (clojure-ts--anon-fn-node-p parent))
        (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)))
          (when-let* ((rule (clojure-ts--find-semantic-rule node parent 0)))
            (and (not (clojure-ts--match-with-metadata node))
@@ -1265,7 +1281,8 @@ according to the rule.  If NODE is nil, use next node 
after BOL."
 
 (defun clojure-ts--match-function-call-arg (node parent _bol)
   "Match NODE if PARENT is a list expressing a function or macro call."
-  (and (clojure-ts--list-node-p parent)
+  (and (or (clojure-ts--list-node-p parent)
+           (clojure-ts--anon-fn-node-p parent))
        ;; Can the following two clauses be replaced by checking indexes?
        ;; Does the second child exist, and is it not equal to the current node?
        (treesit-node-child parent 1 t)
@@ -1284,7 +1301,8 @@ according to the rule.  If NODE is nil, use next node 
after BOL."
   "Match NODE if it is an argument to a PARENT threading macro."
   ;; We want threading macros to indent 2 only if the ->> is on it's own line.
   ;; If not, then align function arg.
-  (and (clojure-ts--list-node-p parent)
+  (and (or (clojure-ts--list-node-p parent)
+           (clojure-ts--anon-fn-node-p parent))
        (let ((first-child (treesit-node-child parent 0 t)))
          (clojure-ts--symbol-matches-p
           clojure-ts--threading-macro
@@ -1335,7 +1353,7 @@ according to the rule.  If NODE is nil, use next node 
after BOL."
     (and prev-sibling
          (clojure-ts--metadata-node-p prev-sibling))))
 
-(defun clojure-ts--anchor-parent-skip-metadata (_node parent _bol)
+(defun clojure-ts--anchor-parent-opening-paren (_node parent _bol)
   "Return position of PARENT start for NODE.
 
 If PARENT has optional metadata we skip it and return starting position
@@ -1343,11 +1361,9 @@ of the first child's opening paren.
 
 NOTE: This serves as an anchor function to resolve an indentation issue
 for forms with type hints."
-  (let ((first-child (treesit-node-child parent 0 t)))
-    (if (clojure-ts--metadata-node-p first-child)
-        ;; We don't need named node here
-        (treesit-node-start (treesit-node-child parent 1))
-      (treesit-node-start parent))))
+  (thread-first parent
+                (treesit-search-subtree #'clojure-ts--opening-paren-node-p nil 
t 1)
+                (treesit-node-start)))
 
 (defun clojure-ts--match-collection-item-with-metadata (node-type)
   "Return a matcher for a collection item with metadata by NODE-TYPE.
@@ -1359,6 +1375,18 @@ if NODE has metadata and its parent has type NODE-TYPE."
                   (treesit-node-type
                    (clojure-ts--node-with-metadata-parent node)))))
 
+(defun clojure-ts--anchor-nth-sibling (n &optional named)
+  "Return the start of the Nth child of PARENT.
+
+NAMED non-nil means count only named nodes.
+
+NOTE: This is a replacement for built-in `nth-sibling' anchor preset,
+which doesn't work properly for named nodes (see the bug
+https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)"
+  (lambda (_n parent &rest _)
+    (treesit-node-start
+     (treesit-node-child parent n named))))
+
 (defun clojure-ts--semantic-indent-rules ()
   "Return a list of indentation rules for `treesit-simple-indent-rules'."
   `((clojure
@@ -1385,11 +1413,11 @@ if NODE has metadata and its parent has type NODE-TYPE."
      ((parent-is "read_cond_lit") parent 3)
      ((parent-is "tagged_or_ctor_lit") parent 0)
      ;; https://guide.clojure.style/#body-indentation
-     (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2)
+     (clojure-ts--match-form-body clojure-ts--anchor-parent-opening-paren 2)
      ;; https://guide.clojure.style/#threading-macros-alignment
      (clojure-ts--match-threading-macro-arg prev-sibling 0)
      ;; https://guide.clojure.style/#vertically-align-fn-args
-     (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0)
+     (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 
t) 0)
      ;; https://guide.clojure.style/#one-space-indent
      ((parent-is "list_lit") parent 1))))
 
@@ -1561,6 +1589,14 @@ have changed."
                                           ((list_lit
                                             ((sym_lit) @sym
                                              (:match 
,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym)))
+                                           @cond)
+                                          ((anon_fn_lit
+                                            ((sym_lit) @sym
+                                             (:match 
,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym))
+                                            (vec_lit) @bindings-vec))
+                                          ((anon_fn_lit
+                                            ((sym_lit) @sym
+                                             (:match 
,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym)))
                                            @cond))
                                         (when 
clojure-ts-align-reader-conditionals
                                           '(((read_cond_lit) @read-cond)
diff --git a/test/clojure-ts-mode-font-lock-test.el 
b/test/clojure-ts-mode-font-lock-test.el
index 02e0fa4e57..05eba9e132 100644
--- a/test/clojure-ts-mode-font-lock-test.el
+++ b/test/clojure-ts-mode-font-lock-test.el
@@ -169,6 +169,10 @@ DESCRIPTION is the description of the spec."
      (2 5 font-lock-type-face)
      (8 9 font-lock-keyword-face)))
 
+  (when-fontifying-it "function literals"
+    ("#(or one two)"
+     (3 4 font-lock-keyword-face)))
+
   (when-fontifying-it "should highlight function name in all known forms"
     ("(letfn [(add [x y]
           (+ x y))
diff --git a/test/clojure-ts-mode-indentation-test.el 
b/test/clojure-ts-mode-indentation-test.el
index fe181f9c63..942175a209 100644
--- a/test/clojure-ts-mode-indentation-test.el
+++ b/test/clojure-ts-mode-indentation-test.el
@@ -184,6 +184,12 @@ DESCRIPTION is a string with the description of the spec."
 (#'foo 5
        6)")
 
+(when-indenting-it "should support function literals"
+  "
+#(or true
+     false
+     %)")
+
 (when-indenting-it "should support block-0 expressions"
   "
 (do (aligned)
@@ -462,6 +468,17 @@ b |20])"
 (let [a b
       c d])")
 
+  (when-aligning-it "should handle function literals"
+    "
+#(let [hello 1
+       foo   \"hone\"]
+   (pringln hello))"
+
+    "
+^{:some :metadata} #(let [foo     %
+                          bar-zzz %]
+                      foo)")
+
   (when-aligning-it "should handle a blank line"
     "
 (let [this-is-a-form b
diff --git a/test/samples/test.clj b/test/samples/test.clj
index 842ff5a71f..18ead86cd2 100644
--- a/test/samples/test.clj
+++ b/test/samples/test.clj
@@ -41,6 +41,19 @@
 
        0 0i)
 
+;; Function literals
+
+^{:some "metadata"} #(let [foo     %
+                           bar-zzz %]
+                       foo)
+
+#(or one
+     two)
+
+#(let [hello 1
+       foo   "hone"]
+   (pringln hello))
+
 ;; examples of valid namespace definitions
 (comment
   (ns .validns)

Reply via email to