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
Show file tree
Hide file tree
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
Expand Up @@ -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)

Expand Down
58 changes: 47 additions & 11 deletions clojure-ts-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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))))

Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions test/clojure-ts-mode-font-lock-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
17 changes: 17 additions & 0 deletions test/clojure-ts-mode-indentation-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions test/samples/test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down