@@ -266,6 +266,12 @@ values like this:
266266 :safe #'listp
267267 :type '(repeat string))
268268
269+ (defcustom clojure-ts-completion-enabled t
270+ " Enable built-in completion feature."
271+ :package-version '(clojure-ts-mode . " 0.5" )
272+ :safe #'booleanp
273+ :type 'boolean )
274+
269275(defvar clojure-ts-mode-remappings
270276 '((clojure-mode . clojure-ts-mode)
271277 (clojurescript-mode . clojure-ts-clojurescript-mode)
@@ -1561,26 +1567,28 @@ function literal."
15611567 " map_lit" " ns_map_lit" " vec_lit" " set_lit" )
15621568 " A regular expression that matches nodes that can be treated as lists." )
15631569
1570+ (defconst clojure-ts--defun-symbols-regex
1571+ (rx bol
1572+ (or " def"
1573+ " defn"
1574+ " defn-"
1575+ " definline"
1576+ " defrecord"
1577+ " defmacro"
1578+ " defmulti"
1579+ " defonce"
1580+ " defprotocol"
1581+ " deftest"
1582+ " deftest-"
1583+ " ns"
1584+ " definterface"
1585+ " deftype"
1586+ " defstruct" )
1587+ eol))
1588+
15641589(defun clojure-ts--defun-node-p (node )
15651590 " Return TRUE if NODE is a function or a var definition."
1566- (clojure-ts--list-node-sym-match-p node
1567- (rx bol
1568- (or " def"
1569- " defn"
1570- " defn-"
1571- " definline"
1572- " defrecord"
1573- " defmacro"
1574- " defmulti"
1575- " defonce"
1576- " defprotocol"
1577- " deftest"
1578- " deftest-"
1579- " ns"
1580- " definterface"
1581- " deftype"
1582- " defstruct" )
1583- eol)))
1591+ (clojure-ts--list-node-sym-match-p node clojure-ts--defun-symbols-regex))
15841592
15851593(defconst clojure-ts--markdown-inline-sexp-nodes
15861594 '(" inline_link" " full_reference_link" " collapsed_reference_link"
@@ -2512,6 +2520,126 @@ before DELIM-OPEN."
25122520 map)
25132521 " Keymap for `clojure-ts-mode' ." )
25142522
2523+ ; ;; Completion
2524+
2525+ (defconst clojure-ts--completion-query-defuns
2526+ (treesit-query-compile 'clojure
2527+ `((source
2528+ (list_lit
2529+ ((sym_lit) @sym
2530+ (:match , clojure-ts--defun-symbols-regex @sym))
2531+ :anchor [(comment) (meta_lit) (old_meta_lit)] :*
2532+ :anchor ((sym_lit) @defun-candidate)))))
2533+ " Query that matches top-level definitions." )
2534+
2535+ (defconst clojure-ts--completion-defn-with-args-sym-regex
2536+ (rx bol
2537+ (or " defn"
2538+ " defn-"
2539+ " fn"
2540+ " fn*"
2541+ " defmacro"
2542+ " defmethod" )
2543+ eol)
2544+ " Regexp that matches a symbol of definition with arguments vector." )
2545+
2546+ (defconst clojure-ts--completion-let-like-sym-regex
2547+ (rx bol
2548+ (or " let"
2549+ " if-let"
2550+ " when-let"
2551+ " if-some"
2552+ " when-some"
2553+ " loop"
2554+ " with-open"
2555+ " dotimes"
2556+ " with-local-vars" )
2557+ eol)
2558+ " Regexp that matches a symbol of let-like form." )
2559+
2560+ (defconst clojure-ts--completion-locals-query
2561+ (treesit-query-compile 'clojure `((vec_lit (sym_lit) @local-candidate)
2562+ (map_lit (sym_lit) @local-candidate)))
2563+ " Query that matches a local binding symbol.
2564+
2565+ Symbold must be a direct child of a vector or a map. This query covers
2566+ bindings vector as well as destructuring syntax." )
2567+
2568+ (defconst clojure-ts--completion-annotations
2569+ (list 'defun-candidate " Definition"
2570+ 'local-candidate " Local variable" )
2571+ " Property list of completion candidate type and annotation string." )
2572+
2573+ (defun clojure-ts--completion-annotation-function (candidate )
2574+ " Return annotation for a completion CANDIDATE."
2575+ (thread-last minibuffer-completion-table
2576+ (alist-get candidate)
2577+ (plist-get clojure-ts--completion-annotations)))
2578+
2579+ (defun clojure-ts--completion-defun-with-args-node-p (node )
2580+ " Return non-nil if NODE is a function definition with arguments."
2581+ (when-let* ((sym-name (clojure-ts--list-node-sym-text node)))
2582+ (string-match-p clojure-ts--completion-defn-with-args-sym-regex sym-name)))
2583+
2584+ (defun clojure-ts--completion-fn-args-nodes ()
2585+ " Return a list of captured nodes that represent function arguments.
2586+
2587+ The function traverses the syntax tree upwards and returns nodes from
2588+ all functions along the way."
2589+ (let ((parent-defun (clojure-ts--parent-until #'clojure-ts--completion-defun-with-args-node-p ))
2590+ (captured-nodes))
2591+ (while parent-defun
2592+ (when-let* ((args-vec (clojure-ts--node-child parent-defun " vec_lit" )))
2593+ (setq captured-nodes
2594+ (append captured-nodes
2595+ (treesit-query-capture args-vec clojure-ts--completion-locals-query))
2596+ parent-defun (treesit-parent-until parent-defun
2597+ #'clojure-ts--completion-defun-with-args-node-p ))))
2598+ captured-nodes))
2599+
2600+ (defun clojure-ts--completion-let-like-node-p (node )
2601+ " Return non-nil if NODE is a let-like form."
2602+ (when-let* ((sym-name (clojure-ts--list-node-sym-text node)))
2603+ (string-match-p clojure-ts--completion-let-like-sym-regex sym-name)))
2604+
2605+ (defun clojure-ts--completion-let-locals-nodes ()
2606+ " Return a list of captured nodes that represent bindings in let forms.
2607+
2608+ The function tranverses the syntax tree upwards and returns nodes from
2609+ all let bindings found along the way."
2610+ (let ((parent-let (clojure-ts--parent-until #'clojure-ts--completion-let-like-node-p ))
2611+ (captured-nodes))
2612+ (while parent-let
2613+ (when-let* ((bindings-vec (clojure-ts--node-child parent-let " vec_lit" )))
2614+ (setq captured-nodes
2615+ (append captured-nodes
2616+ (treesit-query-capture bindings-vec clojure-ts--completion-locals-query))
2617+ parent-let (treesit-parent-until parent-let
2618+ #'clojure-ts--completion-let-like-node-p ))))
2619+ captured-nodes))
2620+
2621+ (defun clojure-ts-completion-at-point-function ()
2622+ " Return a completion table for the symbol around point."
2623+ (when-let* ((bounds (bounds-of-thing-at-point 'symbol ))
2624+ (source (treesit-buffer-root-node 'clojure ))
2625+ (nodes (append (treesit-query-capture source clojure-ts--completion-query-defuns)
2626+ (clojure-ts--completion-fn-args-nodes)
2627+ (clojure-ts--completion-let-locals-nodes))))
2628+ (list (car bounds)
2629+ (cdr bounds)
2630+ (thread-last nodes
2631+ ; ; Remove node at point
2632+ (seq-remove (lambda (item ) (= (treesit-node-end (cdr item)) (point ))))
2633+ ; ; Remove unwanted captured nodes
2634+ (seq-filter (lambda (item )
2635+ (not (member (car item) '(sym kwd)))))
2636+ ; ; Produce alist of candidates
2637+ (seq-map (lambda (item ) (cons (treesit-node-text (cdr item) t ) (car item))))
2638+ ; ; Remove duplicated candidates
2639+ (seq-uniq ))
2640+ :exclusive 'no
2641+ :annotation-function #'clojure-ts--completion-annotation-function )))
2642+
25152643(defvar clojure-ts-clojurescript-mode-map
25162644 (let ((map (make-sparse-keymap )))
25172645 (set-keymap-parent map clojure-ts-mode-map)
@@ -2670,7 +2798,10 @@ REGEX-AVAILABLE."
26702798 clojure-ts--imenu-settings)
26712799
26722800 (when (boundp 'treesit-thing-settings ) ; ; Emacs 30+
2673- (setq-local treesit-thing-settings clojure-ts--thing-settings)))
2801+ (setq-local treesit-thing-settings clojure-ts--thing-settings))
2802+
2803+ (when clojure-ts-completion-enabled
2804+ (add-to-list 'completion-at-point-functions #'clojure-ts-completion-at-point-function )))
26742805
26752806;;;### autoload
26762807(define-derived-mode clojure-ts-mode prog-mode " Clojure[TS]"
0 commit comments