@@ -757,6 +757,10 @@ literals with regex grammar."
757757 " Return non-nil if NODE is a Clojure list."
758758 (string-equal " list_lit" (treesit-node-type node)))
759759
760+ (defun clojure-ts--vec-node-p (node )
761+ " Return non-nil if NODE is a Clojure vector."
762+ (string-equal " vec_lit" (treesit-node-type node)))
763+
760764(defun clojure-ts--anon-fn-node-p (node )
761765 " Return non-nil if NODE is a Clojure function literal."
762766 (string-equal " anon_fn_lit" (treesit-node-type node)))
@@ -1471,6 +1475,27 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph."
14711475 (fill-paragraph justify)))
14721476 t ))
14731477
1478+ (defun clojure-ts--list-node-sym-text (node &optional include-anon-fn-lit )
1479+ " Return text of the first child of the NODE if NODE is a list.
1480+
1481+ Return nil if the NODE is not a list or if the first child is not a
1482+ symbol. Optionally if INCLUDE-ANON-FN-LIT is non-nil, return the text
1483+ of the first symbol of a functional literal NODE."
1484+ (when (or (clojure-ts--list-node-p node)
1485+ (and include-anon-fn-lit
1486+ (clojure-ts--anon-fn-node-p node)))
1487+ (when-let* ((first-child (clojure-ts--node-child-skip-metadata node 0 ))
1488+ ((clojure-ts--symbol-node-p first-child)))
1489+ (clojure-ts--named-node-text first-child))))
1490+
1491+ (defun clojure-ts--list-node-sym-match-p (node regex &optional include-anon-fn-lit )
1492+ " Return TRUE if NODE is a list and its first symbol matches the REGEX.
1493+
1494+ Optionally if INCLUDE-ANON-FN-LIT is TRUE, perform the same check for a
1495+ function literal."
1496+ (when-let* ((sym-text (clojure-ts--list-node-sym-text node include-anon-fn-lit)))
1497+ (string-match-p regex sym-text)))
1498+
14741499(defconst clojure-ts--sexp-nodes
14751500 '(" #_" ; ; transpose-sexp near a discard macro moves it around.
14761501 " num_lit" " sym_lit" " kwd_lit" " nil_lit" " bool_lit"
@@ -1490,18 +1515,16 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph."
14901515
14911516(defun clojure-ts--defun-node-p (node )
14921517 " Return TRUE if NODE is a function or a var definition."
1493- (and (clojure-ts--list-node-p node)
1494- (let ((sym (clojure-ts--node-child-skip-metadata node 0 )))
1495- (string-match-p (rx bol
1496- (or " def"
1497- " defn"
1498- " defn-"
1499- " definline"
1500- " defrecord"
1501- " defmacro"
1502- " defmulti" )
1503- eol)
1504- (clojure-ts--named-node-text sym)))))
1518+ (clojure-ts--list-node-sym-match-p node
1519+ (rx bol
1520+ (or " def"
1521+ " defn"
1522+ " defn-"
1523+ " definline"
1524+ " defrecord"
1525+ " defmacro"
1526+ " defmulti" )
1527+ eol)))
15051528
15061529(defconst clojure-ts--markdown-inline-sexp-nodes
15071530 '(" inline_link" " full_reference_link" " collapsed_reference_link"
@@ -1727,19 +1750,23 @@ Forms between BEG and END are aligned according to
17271750
17281751; ;; Refactoring
17291752
1753+ (defun clojure-ts--parent-until (pred )
1754+ " Return the closest parent of node at point that satisfies PRED."
1755+ (when-let* ((node-at-point (treesit-node-at (point ) 'clojure t )))
1756+ (treesit-parent-until node-at-point pred t )))
1757+
1758+ (defun clojure-ts--search-list-form-at-point (sym-regex &optional include-anon-fn-lit )
1759+ " Return the list node at point which first symbol matches SYM-REGEX.
1760+
1761+ If INCLUDE-ANON-FN-LIT is non-nil, this function may also return a
1762+ functional literal node."
1763+ (clojure-ts--parent-until
1764+ (lambda (node )
1765+ (clojure-ts--list-node-sym-match-p node sym-regex include-anon-fn-lit))))
1766+
17301767(defun clojure-ts--threading-sexp-node ()
17311768 " Return list node at point which is a threading expression."
1732- (when-let* ((node-at-point (treesit-node-at (point ) 'clojure t )))
1733- ; ; We don't want to match `cond->' and `cond->>' , so we should define a very
1734- ; ; specific regexp.
1735- (let ((sym-regex (rx bol (* " some" ) " ->" (* " >" ) eol)))
1736- (treesit-parent-until node-at-point
1737- (lambda (node )
1738- (and (or (clojure-ts--list-node-p node)
1739- (clojure-ts--anon-fn-node-p node))
1740- (let ((first-child (treesit-node-child node 0 t )))
1741- (clojure-ts--symbol-matches-p sym-regex first-child))))
1742- t ))))
1769+ (clojure-ts--search-list-form-at-point (rx bol (* " some" ) " ->" (* " >" ) eol) t ))
17431770
17441771(defun clojure-ts--delete-and-extract-sexp ()
17451772 " Delete the surrounding sexp and return it."
@@ -1874,9 +1901,7 @@ With universal argument \\[universal-argument], fully unwinds thread."
18741901 (n)
18751902 (1 )))
18761903 (if-let* ((threading-sexp (clojure-ts--threading-sexp-node))
1877- (sym (thread-first threading-sexp
1878- (treesit-node-child 0 t )
1879- (clojure-ts--named-node-text))))
1904+ (sym (clojure-ts--list-node-sym-text threading-sexp t )))
18801905 (save-excursion
18811906 (let ((beg (thread-first threading-sexp
18821907 (treesit-node-start)
@@ -1962,9 +1987,7 @@ cannot be found."
19621987 (interactive " p" )
19631988 (if-let* ((threading-sexp (clojure-ts--threading-sexp-node))
19641989 ((clojure-ts--threadable-p threading-sexp))
1965- (sym (thread-first threading-sexp
1966- (treesit-node-child 0 t )
1967- (clojure-ts--named-node-text))))
1990+ (sym (clojure-ts--list-node-sym-text threading-sexp t )))
19681991 (let ((beg (thread-first threading-sexp
19691992 (treesit-node-start)
19701993 (copy-marker )))
@@ -2032,6 +2055,135 @@ value is `clojure-ts-thread-all-but-last'."
20322055 " -" ))))
20332056 (user-error " No defun at point" )))
20342057
2058+ (defun clojure-ts--node-child (node predicate )
2059+ " Return the first child of the NODE that matches the PREDICATE.
2060+
2061+ PREDICATE can be a symbol representing a thing in
2062+ `treesit-thing-settings' , or a predicate, like regexp matching node
2063+ type, etc. See `treesit-thing-settings' for more details."
2064+ (thread-last (treesit-node-children node t )
2065+ (seq-find (lambda (child )
2066+ (treesit-node-match-p child predicate t )))))
2067+
2068+ (defun clojure-ts--node-start-skip-metadata (node )
2069+ " Return NODE start position optionally skipping metadata."
2070+ (if (clojure-ts--metadata-node-p (treesit-node-child node 0 t ))
2071+ (treesit-node-start (treesit-node-child node 1 ))
2072+ (treesit-node-start node)))
2073+
2074+ (defun clojure-ts--add-arity-internal (fn-node )
2075+ " Add an arity to a function defined by FN-NODE."
2076+ (let* ((first-coll (clojure-ts--node-child fn-node (rx bol (or " vec_lit" " list_lit" ) eol)))
2077+ (coll-start (clojure-ts--node-start-skip-metadata first-coll))
2078+ (line-parent (thread-first fn-node
2079+ (clojure-ts--node-child-skip-metadata 0 )
2080+ (treesit-node-start)
2081+ (line-number-at-pos )))
2082+ (line-args (line-number-at-pos coll-start))
2083+ (same-line-p (= line-parent line-args))
2084+ (single-arity-p (clojure-ts--vec-node-p first-coll)))
2085+ (goto-char coll-start)
2086+ (when same-line-p
2087+ (newline-and-indent ))
2088+ (when single-arity-p
2089+ (insert-pair 2 ?\( ?\) )
2090+ (backward-up-list ))
2091+ (insert " ([])\n " )
2092+ ; ; Put the point between square brackets.
2093+ (down-list -2 )))
2094+
2095+ (defun clojure-ts--add-arity-defprotocol-internal (fn-node )
2096+ " Add an arity to a defprotocol function defined by FN-NODE."
2097+ (let* ((args-vec (clojure-ts--node-child fn-node (rx bol " vec_lit" eol)))
2098+ (args-vec-start (clojure-ts--node-start-skip-metadata args-vec))
2099+ (line-parent (thread-first fn-node
2100+ (clojure-ts--node-child-skip-metadata 0 )
2101+ (treesit-node-start)
2102+ (line-number-at-pos )))
2103+ (line-args-vec (line-number-at-pos args-vec-start))
2104+ (same-line-p (= line-parent line-args-vec)))
2105+ (goto-char args-vec-start)
2106+ (insert " []" )
2107+ (if same-line-p
2108+ (insert " " )
2109+ ; ; If args vector is not at the same line, respect this and place each new
2110+ ; ; vector on a new line.
2111+ (newline-and-indent ))
2112+ ; ; Put the point between square brackets.
2113+ (down-list -1 )))
2114+
2115+ (defun clojure-ts--add-arity-reify-internal (fn-node )
2116+ " Add an arity to a reify function defined by FN-NODE."
2117+ (let* ((fn-name (clojure-ts--list-node-sym-text fn-node)))
2118+ (goto-char (clojure-ts--node-start-skip-metadata fn-node))
2119+ (insert " (" fn-name " [])" )
2120+ (newline-and-indent )
2121+ ; ; Put the point between sqare brackets.
2122+ (down-list -2 )))
2123+
2124+ (defun clojure-ts--letfn-defn-p (node )
2125+ " Return non-nil if NODE is a function definition in a letfn form."
2126+ (when-let* ((parent (treesit-node-parent node)))
2127+ (and (clojure-ts--list-node-p node)
2128+ (clojure-ts--vec-node-p parent)
2129+ (let ((grandparent (treesit-node-parent parent)))
2130+ (string= (clojure-ts--list-node-sym-text grandparent)
2131+ " letfn" )))))
2132+
2133+ (defun clojure-ts--proxy-defn-p (node )
2134+ " Return non-nil if NODE is a function definition in a proxy form."
2135+ (when-let* ((parent (treesit-node-parent node)))
2136+ (and (clojure-ts--list-node-p node)
2137+ (string= (clojure-ts--list-node-sym-text parent) " proxy" ))))
2138+
2139+ (defun clojure-ts--defprotocol-defn-p (node )
2140+ " Return non-nil if NODE is a function definition in a defprotocol form."
2141+ (when-let* ((parent (treesit-node-parent node)))
2142+ (and (clojure-ts--list-node-p node)
2143+ (string= (clojure-ts--list-node-sym-text parent) " defprotocol" ))))
2144+
2145+ (defun clojure-ts--reify-defn-p (node )
2146+ " Return non-nil if NODE is a function definition in a reify form."
2147+ (when-let* ((parent (treesit-node-parent node)))
2148+ (and (clojure-ts--list-node-p node)
2149+ (string= (clojure-ts--list-node-sym-text parent) " reify" ))))
2150+
2151+ (defun clojure-ts-add-arity ()
2152+ " Add an arity to a function or macro."
2153+ (interactive )
2154+ (if-let* ((sym-regex (rx bol
2155+ (or " defn"
2156+ " letfn"
2157+ " fn"
2158+ " defmacro"
2159+ " defmethod"
2160+ " defprotocol"
2161+ " reify"
2162+ " proxy" )
2163+ eol))
2164+ (parent-def-node (clojure-ts--search-list-form-at-point sym-regex))
2165+ (parent-def-sym (clojure-ts--list-node-sym-text parent-def-node))
2166+ (fn-node (cond
2167+ ((string= parent-def-sym " letfn" )
2168+ (clojure-ts--parent-until #'clojure-ts--letfn-defn-p ))
2169+ ((string= parent-def-sym " proxy" )
2170+ (clojure-ts--parent-until #'clojure-ts--proxy-defn-p ))
2171+ ((string= parent-def-sym " defprotocol" )
2172+ (clojure-ts--parent-until #'clojure-ts--defprotocol-defn-p ))
2173+ ((string= parent-def-sym " reify" )
2174+ (clojure-ts--parent-until #'clojure-ts--reify-defn-p ))
2175+ (t parent-def-node))))
2176+ (let ((beg-marker (copy-marker (treesit-node-start parent-def-node)))
2177+ (end-marker (copy-marker (treesit-node-end parent-def-node))))
2178+ (cond
2179+ ((string= parent-def-sym " defprotocol" )
2180+ (clojure-ts--add-arity-defprotocol-internal fn-node))
2181+ ((string= parent-def-sym " reify" )
2182+ (clojure-ts--add-arity-reify-internal fn-node))
2183+ (t (clojure-ts--add-arity-internal fn-node)))
2184+ (indent-region beg-marker end-marker))
2185+ (user-error " No suitable form to add an arity at point" )))
2186+
20352187(defun clojure-ts-cycle-keyword-string ()
20362188 " Convert the string at point to a keyword, or vice versa."
20372189 (interactive )
@@ -2141,6 +2293,8 @@ before DELIM-OPEN."
21412293 (keymap-set map " [" #'clojure-ts-convert-collection-to-vector )
21422294 (keymap-set map " C-#" #'clojure-ts-convert-collection-to-set )
21432295 (keymap-set map " #" #'clojure-ts-convert-collection-to-set )
2296+ (keymap-set map " C-a" #'clojure-ts-add-arity )
2297+ (keymap-set map " a" #'clojure-ts-add-arity )
21442298 map)
21452299 " Keymap for `clojure-ts-mode' refactoring commands." )
21462300
@@ -2155,6 +2309,7 @@ before DELIM-OPEN."
21552309 [" Toggle between string & keyword" clojure-ts-cycle-keyword-string]
21562310 [" Align expression" clojure-ts-align]
21572311 [" Cycle privacy" clojure-ts-cycle-privacy]
2312+ [" Add function/macro arity" clojure-ts-add-arity]
21582313 (" Convert collection"
21592314 [" Convert to list" clojure-ts-convert-collection-to-list]
21602315 [" Convert to quoted list" clojure-ts-convert-collection-to-quoted-list]
0 commit comments