Skip to content

Commit 383e08b

Browse files
committed
[#74] Add imenu support for keyword definitions
1 parent 9542792 commit 383e08b

File tree

5 files changed

+75
-12
lines changed

5 files changed

+75
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules.
1919
- [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form.
2020
- [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`.
21+
- [#74](https://github.com/clojure-emacs/clojure-ts-mode/issues/74): Add imenu support for keywords definitions.
2122

2223
## 0.2.3 (2025-03-04)
2324

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,19 @@ Every new line in the docstrings is indented by
257257
`clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default
258258
which matches the `clojure-mode` settings).
259259

260+
#### imenu
261+
262+
`clojure-ts-mode` supports various types of definition that can be navigated
263+
using `imenu`, such as:
264+
265+
- namespace
266+
- function
267+
- macro
268+
- var
269+
- interface (forms such as `defprotocol`, `definterface` and `defmulti`)
270+
- class (forms such as `deftype`, `defrecord` and `defstruct`)
271+
- keyword (for example, spec definitions)
272+
260273
## Migrating to clojure-ts-mode
261274

262275
If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly.

clojure-ts-mode.el

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -657,17 +657,33 @@ See `clojure-ts--definition-node-p' when an exact match is possible."
657657
(and
658658
(clojure-ts--list-node-p node)
659659
(let* ((child (clojure-ts--node-child-skip-metadata node 0))
660-
(child-txt (clojure-ts--named-node-text child)))
660+
(child-txt (clojure-ts--named-node-text child))
661+
(name-sym (clojure-ts--node-child-skip-metadata node 1)))
661662
(and (clojure-ts--symbol-node-p child)
663+
(clojure-ts--symbol-node-p name-sym)
662664
(string-match-p definition-type-regexp child-txt)))))
663665

666+
(defun clojure-ts--kwd-definition-node-match-p (node)
667+
"Return non-nil if the NODE is a keyword definition."
668+
(and (clojure-ts--list-node-p node)
669+
(let* ((child (clojure-ts--node-child-skip-metadata node 0))
670+
(child-txt (clojure-ts--named-node-text child))
671+
(child-ns (clojure-ts--node-namespace-text child))
672+
(name-kwd (clojure-ts--node-child-skip-metadata node 1)))
673+
(and child-ns
674+
(clojure-ts--symbol-node-p child)
675+
(clojure-ts--keyword-node-p name-kwd)
676+
(string-equal child-txt "def")))))
677+
664678
(defun clojure-ts--standard-definition-node-name (node)
665679
"Return the definition name for the given NODE.
666-
Returns nil if NODE is not a list with symbols as the first two children.
667-
For example the node representing the expression (def foo 1) would return foo.
668-
The node representing (ns user) would return user.
669-
Does not does any matching on the first symbol (def, defn, etc), so identifying
670-
that a node is a definition is intended to be done elsewhere.
680+
681+
Returns nil if NODE is not a list with symbols as the first two
682+
children. For example the node representing the expression (def foo 1)
683+
would return foo. The node representing (ns user) would return user.
684+
Does not do any matching on the first symbol (def, defn, etc), so
685+
identifying that a node is a definition is intended to be done
686+
elsewhere.
671687
672688
Can be called directly, but intended for use as `treesit-defun-name-function'."
673689
(when (and (clojure-ts--list-node-p node)
@@ -683,6 +699,21 @@ Can be called directly, but intended for use as `treesit-defun-name-function'."
683699
(concat (treesit-node-text ns) "/" (treesit-node-text name))
684700
(treesit-node-text name)))))))
685701

702+
(defun clojure-ts--kwd-definition-node-name (node)
703+
"Return the keyword name for the given NODE.
704+
705+
Returns nil if NODE is not a list where the first element is a symbol
706+
and the second is a keyword. For example, a node representing the
707+
expression (s/def ::foo int?) would return foo.
708+
709+
Can be called directly, but intended for use as
710+
`treesit-defun-name-function'."
711+
(when (and (clojure-ts--list-node-p node)
712+
(clojure-ts--symbol-node-p (clojure-ts--node-child-skip-metadata node 0)))
713+
(let ((kwd (clojure-ts--node-child-skip-metadata node 1)))
714+
(when (clojure-ts--keyword-node-p kwd)
715+
(treesit-node-text (treesit-node-child-by-field-name kwd "name"))))))
716+
686717
(defvar clojure-ts--function-type-regexp
687718
(rx string-start (or (seq "defn" (opt "-")) "defmethod" "deftest") string-end)
688719
"Regular expression for matching definition nodes that resemble functions.")
@@ -733,7 +764,6 @@ Includes a dispatch value when applicable (defmethods)."
733764
"Return non-nil if NODE represents a protocol or interface definition."
734765
(clojure-ts--definition-node-match-p clojure-ts--interface-type-regexp node))
735766

736-
737767
(defvar clojure-ts--imenu-settings
738768
`(("Namespace" "list_lit" clojure-ts--ns-node-p)
739769
("Function" "list_lit" clojure-ts--function-node-p
@@ -742,7 +772,11 @@ Includes a dispatch value when applicable (defmethods)."
742772
("Macro" "list_lit" clojure-ts--defmacro-node-p)
743773
("Variable" "list_lit" clojure-ts--variable-definition-node-p)
744774
("Interface" "list_lit" clojure-ts--interface-node-p)
745-
("Class" "list_lit" clojure-ts--class-node-p))
775+
("Class" "list_lit" clojure-ts--class-node-p)
776+
("Keyword"
777+
"list_lit"
778+
clojure-ts--kwd-definition-node-match-p
779+
clojure-ts--kwd-definition-node-name))
746780
"The value for `treesit-simple-imenu-settings'.
747781
By default `treesit-defun-name-function' is used to extract definition names.
748782
See `clojure-ts--standard-definition-node-name' for the implementation used.")

test/clojure-ts-mode-imenu-test.el

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@
2929
(describe "clojure-ts-mode imenu integration"
3030
(it "should index def with meta data"
3131
(with-clojure-ts-buffer "^{:foo 1}(def a 1)"
32-
(expect (imenu--in-alist "a" (imenu--make-index-alist))
33-
:not :to-be nil)))
32+
(let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t)))
33+
(expect (imenu-find-default "a" flatten-index)
34+
:to-equal "Variable:a"))))
3435

3536
(it "should index defn with meta data"
3637
(with-clojure-ts-buffer "^{:foo 1}(defn a [])"
37-
(expect (imenu--in-alist "a" (imenu--make-index-alist))
38-
:not :to-be nil))))
38+
(let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t)))
39+
(expect (imenu-find-default "a" flatten-index)
40+
:to-equal "Function:a"))))
41+
42+
(it "should index def with keywords as a first item"
43+
(with-clojure-ts-buffer "(s/def ::username string?)"
44+
(let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t)))
45+
(expect (imenu-find-default "username" flatten-index)
46+
:to-equal "Keyword:username")))))

test/samples/spec.clj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(ns spec
2+
(:require
3+
[clojure.spec.alpha :as s]))
4+
5+
(s/def ::username string?)
6+
(s/def ::age number?)
7+
(s/def ::email string?)

0 commit comments

Comments
 (0)