Skip to content

Commit 7c33f29

Browse files
rrudakovbbatsov
authored andcommitted
Extend built-in completion
Complete keywords and local bindings in `for` and `doseq` forms.
1 parent 43f7a67 commit 7c33f29

File tree

4 files changed

+86
-12
lines changed

4 files changed

+86
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`).
66
- [#113](https://github.com/clojure-emacs/clojure-ts-mode/pull/113): Fix non-working refactoring commands for Emacs-30.
7+
- [#114](https://github.com/clojure-emacs/clojure-ts-mode/pull/114): Extend built-in completion to complete keywords and local bindings in
8+
`for` and `doseq` forms.
79

810
## 0.5.1 (2025-06-17)
911

clojure-ts-mode.el

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,6 +2592,10 @@ before DELIM-OPEN."
25922592
:anchor ((sym_lit) @defun-candidate)))))
25932593
"Query that matches top-level definitions.")
25942594

2595+
(defconst clojure-ts--completion-query-keywords
2596+
(treesit-query-compile 'clojure '((kwd_lit) @keyword-candidate))
2597+
"Query that matches any Clojure keyword.")
2598+
25952599
(defconst clojure-ts--completion-defn-with-args-sym-regex
25962600
(rx bol
25972601
(or "defn"
@@ -2613,7 +2617,9 @@ before DELIM-OPEN."
26132617
"loop"
26142618
"with-open"
26152619
"dotimes"
2616-
"with-local-vars")
2620+
"with-local-vars"
2621+
"for"
2622+
"doseq")
26172623
eol)
26182624
"Regexp that matches a symbol of let-like form.")
26192625

@@ -2627,7 +2633,8 @@ bindings vector as well as destructuring syntax.")
26272633

26282634
(defconst clojure-ts--completion-annotations
26292635
(list 'defun-candidate " Definition"
2630-
'local-candidate " Local variable")
2636+
'local-candidate " Local variable"
2637+
'keyword-candidate " Keyword")
26312638
"Property list of completion candidate type and annotation string.")
26322639

26332640
(defun clojure-ts--completion-annotation-function (candidate)
@@ -2652,9 +2659,9 @@ all functions along the way."
26522659
(when-let* ((args-vec (clojure-ts--node-child parent-defun "vec_lit")))
26532660
(setq captured-nodes
26542661
(append captured-nodes
2655-
(treesit-query-capture args-vec clojure-ts--completion-locals-query))
2656-
parent-defun (treesit-parent-until parent-defun
2657-
#'clojure-ts--completion-defun-with-args-node-p))))
2662+
(treesit-query-capture args-vec clojure-ts--completion-locals-query))))
2663+
(setq parent-defun (treesit-parent-until parent-defun
2664+
#'clojure-ts--completion-defun-with-args-node-p)))
26582665
captured-nodes))
26592666

26602667
(defun clojure-ts--completion-let-like-node-p (node)
@@ -2673,16 +2680,17 @@ all let bindings found along the way."
26732680
(when-let* ((bindings-vec (clojure-ts--node-child parent-let "vec_lit")))
26742681
(setq captured-nodes
26752682
(append captured-nodes
2676-
(treesit-query-capture bindings-vec clojure-ts--completion-locals-query))
2677-
parent-let (treesit-parent-until parent-let
2678-
#'clojure-ts--completion-let-like-node-p))))
2683+
(treesit-query-capture bindings-vec clojure-ts--completion-locals-query))))
2684+
(setq parent-let (treesit-parent-until parent-let
2685+
#'clojure-ts--completion-let-like-node-p)))
26792686
captured-nodes))
26802687

26812688
(defun clojure-ts-completion-at-point-function ()
26822689
"Return a completion table for the symbol around point."
26832690
(when-let* ((bounds (bounds-of-thing-at-point 'symbol))
26842691
(source (treesit-buffer-root-node 'clojure))
26852692
(nodes (append (treesit-query-capture source clojure-ts--completion-query-defuns)
2693+
(treesit-query-capture source clojure-ts--completion-query-keywords)
26862694
(clojure-ts--completion-fn-args-nodes)
26872695
(clojure-ts--completion-let-locals-nodes))))
26882696
(list (car bounds)
@@ -2692,7 +2700,7 @@ all let bindings found along the way."
26922700
(seq-remove (lambda (item) (= (treesit-node-end (cdr item)) (point))))
26932701
;; Remove unwanted captured nodes
26942702
(seq-filter (lambda (item)
2695-
(not (member (car item) '(sym kwd)))))
2703+
(not (equal (car item) 'sym))))
26962704
;; Produce alist of candidates
26972705
(seq-map (lambda (item) (cons (treesit-node-text (cdr item) t) (car item))))
26982706
;; Remove duplicated candidates

test/clojure-ts-mode-completion.el

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ b|"
4646
(expect (nth 2 (clojure-ts-completion-at-point-function))
4747
:to-equal '(("foo" . defun-candidate)
4848
("bar" . defun-candidate)
49-
("baz" . defun-candidate)))))
49+
("baz" . defun-candidate)
50+
(":first" . keyword-candidate)
51+
(":second" . keyword-candidate)))))
5052

5153
(it "should complete function arguments"
5254
(with-clojure-ts-buffer-point "
@@ -61,6 +63,8 @@ b|"
6163
:to-equal '(("foo" . defun-candidate)
6264
("bar" . defun-candidate)
6365
("baz" . defun-candidate)
66+
(":first" . keyword-candidate)
67+
(":second" . keyword-candidate)
6468
("username" . local-candidate)))))
6569

6670
(it "should not complete function arguments outside of function"
@@ -77,7 +81,9 @@ u|"
7781
(expect (nth 2 (clojure-ts-completion-at-point-function))
7882
:to-equal '(("foo" . defun-candidate)
7983
("bar" . defun-candidate)
80-
("baz" . defun-candidate)))))
84+
("baz" . defun-candidate)
85+
(":first" . keyword-candidate)
86+
(":second" . keyword-candidate)))))
8187

8288
(it "should complete destructured function arguments"
8389
(with-clojure-ts-buffer-point "
@@ -86,6 +92,7 @@ u|"
8692
(println u|))"
8793
(expect (nth 2 (clojure-ts-completion-at-point-function))
8894
:to-equal '(("baz" . defun-candidate)
95+
(":keys" . keyword-candidate)
8996
("username" . local-candidate))))
9097

9198
(with-clojure-ts-buffer-point "
@@ -94,6 +101,7 @@ u|"
94101
(println u|))"
95102
(expect (nth 2 (clojure-ts-completion-at-point-function))
96103
:to-equal '(("baz" . defun-candidate)
104+
(":strs" . keyword-candidate)
97105
("username" . local-candidate))))
98106

99107
(with-clojure-ts-buffer-point "
@@ -102,6 +110,7 @@ u|"
102110
(println u|))"
103111
(expect (nth 2 (clojure-ts-completion-at-point-function))
104112
:to-equal '(("baz" . defun-candidate)
113+
(":syms" . keyword-candidate)
105114
("username" . local-candidate))))
106115

107116
(with-clojure-ts-buffer-point "
@@ -110,6 +119,7 @@ u|"
110119
(println u|))"
111120
(expect (nth 2 (clojure-ts-completion-at-point-function))
112121
:to-equal '(("baz" . defun-candidate)
122+
(":name" . keyword-candidate)
113123
("username" . local-candidate))))
114124

115125
(with-clojure-ts-buffer-point "
@@ -131,6 +141,9 @@ u|"
131141
a|))"
132142
(expect (nth 2 (clojure-ts-completion-at-point-function))
133143
:to-equal '(("baz" . defun-candidate)
144+
(":street" . keyword-candidate)
145+
(":zip-code" . keyword-candidate)
146+
(":keys" . keyword-candidate)
134147
("first-name" . local-candidate)
135148
("last-name" . local-candidate)
136149
("address" . local-candidate)
@@ -147,7 +160,43 @@ u|"
147160
(expect (nth 2 (clojure-ts-completion-at-point-function))
148161
:to-equal '(("baz" . defun-candidate)
149162
("first-name" . local-candidate)
150-
("full-name" . local-candidate))))))
163+
("full-name" . local-candidate)))))
164+
165+
(it "should complete any keyword"
166+
(with-clojure-ts-buffer-point "
167+
(defn baz
168+
[first-name]
169+
(let [last-name \"Doe\"
170+
address {:street \"Whatever\" :zip-code 2222}
171+
{:keys [street zip-code]} address]
172+
(println street zip-code)))
173+
174+
:|"
175+
(expect (nth 2 (clojure-ts-completion-at-point-function))
176+
:to-equal '(("baz" . defun-candidate)
177+
(":street" . keyword-candidate)
178+
(":zip-code" . keyword-candidate)
179+
(":keys" . keyword-candidate)))))
180+
181+
(it "should complete locals of for bindings"
182+
(with-clojure-ts-buffer-point "
183+
(for [digit [\"one\" \"two\" \"three\"]
184+
:let [prefixed-digit (str \"hello-\" digit)]]
185+
(println d|))"
186+
(expect (nth 2 (clojure-ts-completion-at-point-function))
187+
:to-equal '((":let" . keyword-candidate)
188+
("digit" . local-candidate)
189+
("prefixed-digit" . local-candidate)))))
190+
191+
(it "should complete locals of doseq bindings"
192+
(with-clojure-ts-buffer-point "
193+
(doseq [digit [\"one\" \"two\" \"three\"]
194+
:let [prefixed-digit (str \"hello-\" digit)]]
195+
(println d|))"
196+
(expect (nth 2 (clojure-ts-completion-at-point-function))
197+
:to-equal '((":let" . keyword-candidate)
198+
("digit" . local-candidate)
199+
("prefixed-digit" . local-candidate))))))
151200

152201
(provide 'clojure-ts-mode-completion)
153202
;;; clojure-ts-mode-completion.el ends here

test/samples/completion.clj

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,18 @@
5454
;; Both arguments are available here.
5555
(= item top-arg))
5656
[1 2 3 4 5]))
57+
58+
;; Works for top-level bindings and for nested `:let` bindings.
59+
(for [digit vec-variable
60+
:let [prefixed-digit (str "hello-" digit)]]
61+
(println prefixed-digit digit))
62+
63+
;; Same for `doseq`
64+
(doseq [word vec-variable
65+
:let [suffixed-word (str "hello-" word)]]
66+
(println suffixed-word word))
67+
68+
;; Can complete any keyword from the buffer
69+
(do :users/usename
70+
:address
71+
:kwd)

0 commit comments

Comments
 (0)