diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8a68839..9c2891d 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -260,7 +260,8 @@ if a third argument (the value) is provided. (defun clojure-ts--docstring-query (capture-symbol) "Return a query that captures docstrings with CAPTURE-SYMBOL." `(;; Captures docstrings in def - ((list_lit :anchor (sym_lit) @_def_symbol + ((list_lit :anchor (meta_lit) :? + :anchor (sym_lit) @_def_symbol :anchor (comment) :? :anchor (sym_lit) ; variable name :anchor (comment) :? @@ -288,7 +289,8 @@ if a third argument (the value) is provided. @_def_symbol) (:equal @_doc-keyword ":doc")) ;; Captures docstrings defn, defmacro, ns, and things like that - ((list_lit :anchor (sym_lit) @_def_symbol + ((list_lit :anchor (meta_lit) :? + :anchor (sym_lit) @_def_symbol :anchor (comment) :? :anchor (sym_lit) ; function_name :anchor (comment) :? @@ -347,7 +349,7 @@ with the markdown_inline grammar." :feature 'builtin :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face)) + `(((list_lit meta: _ :? :anchor (sym_lit (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))) @@ -369,7 +371,8 @@ with the markdown_inline grammar." ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics :feature 'definition ;; defn and defn like macros :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :? + :anchor (sym_lit (sym_name) @def) :anchor (sym_lit (sym_name) @font-lock-function-name-face)) (:match ,(rx-to-string `(seq bol @@ -410,7 +413,8 @@ with the markdown_inline grammar." :feature 'variable ;; def, defonce :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :? + :anchor (sym_lit (sym_name) @def) :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) (:match ,clojure-ts--variable-definition-symbol-regexp @def))) @@ -520,6 +524,10 @@ with the markdown_inline grammar." "Return non-nil if NODE is a Clojure keyword." (string-equal "kwd_lit" (treesit-node-type node))) +(defun clojure-ts--metadata-node-p (node) + "Return non-nil if NODE is a Clojure metadata node." + (string-equal "meta_lit" (treesit-node-type node) )) + (defun clojure-ts--named-node-text (node) "Gets the name of a symbol or keyword NODE. This does not include the NODE's namespace." @@ -530,6 +538,11 @@ This does not include the NODE's namespace." (and (clojure-ts--symbol-node-p node) (string-equal expected-symbol-name (clojure-ts--named-node-text node)))) +(defun clojure-ts--node-child-skip-metadata (node n) + "Return the Nth child of NODE like `treesit-node-child`, skipping the optional metadata node at pos 0 if present." + (let ((first-child (treesit-node-child node 0 t))) + (treesit-node-child node (if (clojure-ts--metadata-node-p first-child) (1+ n) n) t))) + (defun clojure-ts--symbol-matches-p (symbol-regexp node) "Return non-nil if NODE is a symbol that matches SYMBOL-REGEXP." (and (clojure-ts--symbol-node-p node) @@ -550,7 +563,7 @@ like \"defn\". See `clojure-ts--definition-node-p' when an exact match is possible." (and (clojure-ts--list-node-p node) - (let* ((child (treesit-node-child node 0 t)) + (let* ((child (clojure-ts--node-child-skip-metadata node 0)) (child-txt (clojure-ts--named-node-text child))) (and (clojure-ts--symbol-node-p child) (string-match-p definition-type-regexp child-txt))))) @@ -565,8 +578,8 @@ that a node is a definition is intended to be done elsewhere. Can be called directly, but intended for use as `treesit-defun-name-function'." (when (and (clojure-ts--list-node-p node) - (clojure-ts--symbol-node-p (treesit-node-child node 0 t))) - (let ((sym (treesit-node-child node 1 t))) + (clojure-ts--symbol-node-p (clojure-ts--node-child-skip-metadata node 0))) + (let ((sym (clojure-ts--node-child-skip-metadata node 1))) (when (clojure-ts--symbol-node-p sym) ;; Extracts ns and name, and recreates the full var name. ;; We can't just get the node-text of the full symbol because @@ -724,19 +737,20 @@ https://github.com/weavejester/cljfmt/blob/fb26b22f569724b05c93eb2502592dfc2de89 (or (clojure-ts--symbol-node-p first-child) (clojure-ts--keyword-node-p first-child))))) -(defun clojure-ts--match-expression-in-body (_node parent _bol) +(defun clojure-ts--match-expression-in-body (node parent _bol) "Match NODE if it is an expression used in a body argument. PARENT is expected to be a list literal. See `treesit-simple-indent-rules'." (and (clojure-ts--list-node-p parent) - (let ((first-child (treesit-node-child parent 0 t))) + (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) (and (not (clojure-ts--symbol-matches-p ;; Symbols starting with this are false positives (rx line-start (or "default" "deflate" "defer")) first-child)) + (not (clojure-ts--match-with-metadata node)) (clojure-ts--symbol-matches-p clojure-ts--symbols-with-body-expressions-regexp first-child))))) @@ -808,6 +822,12 @@ forms like deftype, defrecord, reify, proxy, etc." (clojure-ts--match-fn-docstring parent) (clojure-ts--match-method-docstring parent)))) +(defun clojure-ts--match-with-metadata (node &optional _parent _bol) + "Match NODE when it has metadata." + (let ((prev-sibling (treesit-node-prev-sibling node))) + (and prev-sibling + (clojure-ts--metadata-node-p prev-sibling)))) + (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'." `((clojure @@ -820,6 +840,7 @@ forms like deftype, defrecord, reify, proxy, etc." (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-with-metadata parent 0) ;; Literal Sequences ((parent-is "list_lit") parent 1) ;; https://guide.clojure.style/#one-space-indent ((parent-is "vec_lit") parent 1) ;; https://guide.clojure.style/#bindings-alignment diff --git a/test/clojure-ts-mode-imenu-test.el b/test/clojure-ts-mode-imenu-test.el new file mode 100644 index 0000000..1dbe71c --- /dev/null +++ b/test/clojure-ts-mode-imenu-test.el @@ -0,0 +1,38 @@ +;;; clojure-ts-mode-util-test.el --- Clojure TS Mode: util test suite -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure TS Mode + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'imenu) + + +(describe "clojure-ts-mode imenu integration" + (it "should index def with meta data" + (with-clojure-ts-buffer "^{:foo 1}(def a 1)" + (expect (imenu--in-alist "a" (imenu--make-index-alist)) + :not :to-be nil))) + + (it "should index defn with meta data" + (with-clojure-ts-buffer "^{:foo 1}(defn a [])" + (expect (imenu--in-alist "a" (imenu--make-index-alist)) + :not :to-be nil)))) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index a5fe041..1854787 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -93,7 +93,7 @@ IProto (foo [this x] x)) -(defn foo2 [x]b) +(defn foo2 [x] b) (reify IProto @@ -118,3 +118,27 @@ ([a] a) ([a b] b))}) + +^:foo +(def a 1) + +^{:foo true} +(def b 2) + +^{:foo true} +[1 2] + +(comment + ^{:a 1} + (def a 2)) + +(defn hinted + (^String []) + (^java.util.List + [a & args])) + +^{:foo true} +(defn c + "hello" + [_foo] + (+ 1 1)) diff --git a/test/samples/test.clj b/test/samples/test.clj index ce4c029..1ab5efa 100644 --- a/test/samples/test.clj +++ b/test/samples/test.clj @@ -289,6 +289,9 @@ clojure.core/map (def ^Integer x 1) +^{:foo true} +(defn b "hello" [] "world") + (comment (defrecord TestRecord [field] AutoCloseable