branch: elpa/clojure-ts-mode
commit 7c33f297788f21f150eb5a0a47fa62baf1e0e5ec
Author: Roman Rudakov <rruda...@fastmail.com>
Commit: Bozhidar Batsov <bozhi...@batsov.dev>

    Extend built-in completion
    
    Complete keywords and local bindings in `for` and `doseq` forms.
---
 CHANGELOG.md                       |  2 ++
 clojure-ts-mode.el                 | 26 +++++++++++-------
 test/clojure-ts-mode-completion.el | 55 +++++++++++++++++++++++++++++++++++---
 test/samples/completion.clj        | 15 +++++++++++
 4 files changed, 86 insertions(+), 12 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 859ff7389d..96251e717f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
 
 - Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`).
 - [#113](https://github.com/clojure-emacs/clojure-ts-mode/pull/113): Fix 
non-working refactoring commands for Emacs-30.
+- [#114](https://github.com/clojure-emacs/clojure-ts-mode/pull/114): Extend 
built-in completion to complete keywords and local bindings in
+  `for` and `doseq` forms.
 
 ## 0.5.1 (2025-06-17)
 
diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el
index 9677f47163..4802d9e4d2 100644
--- a/clojure-ts-mode.el
+++ b/clojure-ts-mode.el
@@ -2592,6 +2592,10 @@ before DELIM-OPEN."
                              :anchor ((sym_lit) @defun-candidate)))))
   "Query that matches top-level definitions.")
 
+(defconst clojure-ts--completion-query-keywords
+  (treesit-query-compile 'clojure '((kwd_lit) @keyword-candidate))
+  "Query that matches any Clojure keyword.")
+
 (defconst clojure-ts--completion-defn-with-args-sym-regex
   (rx bol
       (or "defn"
@@ -2613,7 +2617,9 @@ before DELIM-OPEN."
           "loop"
           "with-open"
           "dotimes"
-          "with-local-vars")
+          "with-local-vars"
+          "for"
+          "doseq")
       eol)
   "Regexp that matches a symbol of let-like form.")
 
@@ -2627,7 +2633,8 @@ bindings vector as well as destructuring syntax.")
 
 (defconst clojure-ts--completion-annotations
   (list 'defun-candidate " Definition"
-        'local-candidate " Local variable")
+        'local-candidate " Local variable"
+        'keyword-candidate " Keyword")
   "Property list of completion candidate type and annotation string.")
 
 (defun clojure-ts--completion-annotation-function (candidate)
@@ -2652,9 +2659,9 @@ all functions along the way."
       (when-let* ((args-vec (clojure-ts--node-child parent-defun "vec_lit")))
         (setq captured-nodes
               (append captured-nodes
-                      (treesit-query-capture args-vec 
clojure-ts--completion-locals-query))
-              parent-defun (treesit-parent-until parent-defun
-                                                 
#'clojure-ts--completion-defun-with-args-node-p))))
+                      (treesit-query-capture args-vec 
clojure-ts--completion-locals-query))))
+      (setq parent-defun (treesit-parent-until parent-defun
+                                               
#'clojure-ts--completion-defun-with-args-node-p)))
     captured-nodes))
 
 (defun clojure-ts--completion-let-like-node-p (node)
@@ -2673,9 +2680,9 @@ all let bindings found along the way."
       (when-let* ((bindings-vec (clojure-ts--node-child parent-let "vec_lit")))
         (setq captured-nodes
               (append captured-nodes
-                      (treesit-query-capture bindings-vec 
clojure-ts--completion-locals-query))
-              parent-let (treesit-parent-until parent-let
-                                               
#'clojure-ts--completion-let-like-node-p))))
+                      (treesit-query-capture bindings-vec 
clojure-ts--completion-locals-query))))
+      (setq parent-let (treesit-parent-until parent-let
+                                             
#'clojure-ts--completion-let-like-node-p)))
     captured-nodes))
 
 (defun clojure-ts-completion-at-point-function ()
@@ -2683,6 +2690,7 @@ all let bindings found along the way."
   (when-let* ((bounds (bounds-of-thing-at-point 'symbol))
               (source (treesit-buffer-root-node 'clojure))
               (nodes (append (treesit-query-capture source 
clojure-ts--completion-query-defuns)
+                             (treesit-query-capture source 
clojure-ts--completion-query-keywords)
                              (clojure-ts--completion-fn-args-nodes)
                              (clojure-ts--completion-let-locals-nodes))))
     (list (car bounds)
@@ -2692,7 +2700,7 @@ all let bindings found along the way."
                        (seq-remove (lambda (item) (= (treesit-node-end (cdr 
item)) (point))))
                        ;; Remove unwanted captured nodes
                        (seq-filter (lambda (item)
-                                     (not (member (car item) '(sym kwd)))))
+                                     (not (equal (car item) 'sym))))
                        ;; Produce alist of candidates
                        (seq-map (lambda (item) (cons (treesit-node-text (cdr 
item) t) (car item))))
                        ;; Remove duplicated candidates
diff --git a/test/clojure-ts-mode-completion.el 
b/test/clojure-ts-mode-completion.el
index 1bc92cec10..ffa30df36a 100644
--- a/test/clojure-ts-mode-completion.el
+++ b/test/clojure-ts-mode-completion.el
@@ -46,7 +46,9 @@ b|"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("foo" . defun-candidate)
                           ("bar" . defun-candidate)
-                          ("baz" . defun-candidate)))))
+                          ("baz" . defun-candidate)
+                          (":first" . keyword-candidate)
+                          (":second" . keyword-candidate)))))
 
   (it "should complete function arguments"
     (with-clojure-ts-buffer-point "
@@ -61,6 +63,8 @@ b|"
               :to-equal '(("foo" . defun-candidate)
                           ("bar" . defun-candidate)
                           ("baz" . defun-candidate)
+                          (":first" . keyword-candidate)
+                          (":second" . keyword-candidate)
                           ("username" . local-candidate)))))
 
   (it "should not complete function arguments outside of function"
@@ -77,7 +81,9 @@ u|"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("foo" . defun-candidate)
                           ("bar" . defun-candidate)
-                          ("baz" . defun-candidate)))))
+                          ("baz" . defun-candidate)
+                          (":first" . keyword-candidate)
+                          (":second" . keyword-candidate)))))
 
   (it "should complete destructured function arguments"
     (with-clojure-ts-buffer-point "
@@ -86,6 +92,7 @@ u|"
   (println u|))"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
+                          (":keys" . keyword-candidate)
                           ("username" . local-candidate))))
 
     (with-clojure-ts-buffer-point "
@@ -94,6 +101,7 @@ u|"
   (println u|))"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
+                          (":strs" . keyword-candidate)
                           ("username" . local-candidate))))
 
     (with-clojure-ts-buffer-point "
@@ -102,6 +110,7 @@ u|"
   (println u|))"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
+                          (":syms" . keyword-candidate)
                           ("username" . local-candidate))))
 
     (with-clojure-ts-buffer-point "
@@ -110,6 +119,7 @@ u|"
   (println u|))"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
+                          (":name" . keyword-candidate)
                           ("username" . local-candidate))))
 
     (with-clojure-ts-buffer-point "
@@ -131,6 +141,9 @@ u|"
     a|))"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
+                          (":street" . keyword-candidate)
+                          (":zip-code" . keyword-candidate)
+                          (":keys" . keyword-candidate)
                           ("first-name" . local-candidate)
                           ("last-name" . local-candidate)
                           ("address" . local-candidate)
@@ -147,7 +160,43 @@ u|"
       (expect (nth 2 (clojure-ts-completion-at-point-function))
               :to-equal '(("baz" . defun-candidate)
                           ("first-name" . local-candidate)
-                          ("full-name" . local-candidate))))))
+                          ("full-name" . local-candidate)))))
+
+  (it "should complete any keyword"
+    (with-clojure-ts-buffer-point "
+(defn baz
+  [first-name]
+  (let [last-name \"Doe\"
+        address {:street \"Whatever\" :zip-code 2222}
+        {:keys [street zip-code]} address]
+    (println street zip-code)))
+
+:|"
+      (expect (nth 2 (clojure-ts-completion-at-point-function))
+              :to-equal '(("baz" . defun-candidate)
+                          (":street" . keyword-candidate)
+                          (":zip-code" . keyword-candidate)
+                          (":keys" . keyword-candidate)))))
+
+  (it "should complete locals of for bindings"
+    (with-clojure-ts-buffer-point "
+(for [digit [\"one\" \"two\" \"three\"]
+      :let  [prefixed-digit (str \"hello-\" digit)]]
+  (println d|))"
+      (expect (nth 2 (clojure-ts-completion-at-point-function))
+              :to-equal '((":let" . keyword-candidate)
+                          ("digit" . local-candidate)
+                          ("prefixed-digit" . local-candidate)))))
+
+  (it "should complete locals of doseq bindings"
+    (with-clojure-ts-buffer-point "
+(doseq [digit [\"one\" \"two\" \"three\"]
+        :let  [prefixed-digit (str \"hello-\" digit)]]
+  (println d|))"
+      (expect (nth 2 (clojure-ts-completion-at-point-function))
+              :to-equal '((":let" . keyword-candidate)
+                          ("digit" . local-candidate)
+                          ("prefixed-digit" . local-candidate))))))
 
 (provide 'clojure-ts-mode-completion)
 ;;; clojure-ts-mode-completion.el ends here
diff --git a/test/samples/completion.clj b/test/samples/completion.clj
index 16b64de570..7207d7f08c 100644
--- a/test/samples/completion.clj
+++ b/test/samples/completion.clj
@@ -54,3 +54,18 @@
             ;; Both arguments are available here.
             (= item top-arg))
           [1 2 3 4 5]))
+
+;; Works for top-level bindings and for nested `:let` bindings.
+(for [digit vec-variable
+      :let  [prefixed-digit (str "hello-" digit)]]
+  (println prefixed-digit digit))
+
+;; Same for `doseq`
+(doseq [word vec-variable
+        :let  [suffixed-word (str "hello-" word)]]
+  (println suffixed-word word))
+
+;; Can complete any keyword from the buffer
+(do :users/usename
+    :address
+    :kwd)

Reply via email to