Skip to content

Fix some issues with short anonymous functions #86

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
merged 1 commit into from
Apr 28, 2025
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

58 changes: 47 additions & 11 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
@@ -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,19 +1353,17 @@ 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
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)
4 changes: 4 additions & 0 deletions test/clojure-ts-mode-font-lock-test.el
Original file line number Diff line number Diff line change
@@ -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))
17 changes: 17 additions & 0 deletions test/clojure-ts-mode-indentation-test.el
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions test/samples/test.clj
Original file line number Diff line number Diff line change
@@ -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)