@@ -757,6 +757,10 @@ literals with regex grammar."
757
757
" Return non-nil if NODE is a Clojure list."
758
758
(string-equal " list_lit" (treesit-node-type node)))
759
759
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
+
760
764
(defun clojure-ts--anon-fn-node-p (node )
761
765
" Return non-nil if NODE is a Clojure function literal."
762
766
(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."
1471
1475
(fill-paragraph justify)))
1472
1476
t ))
1473
1477
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
+
1474
1499
(defconst clojure-ts--sexp-nodes
1475
1500
'(" #_" ; ; transpose-sexp near a discard macro moves it around.
1476
1501
" 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."
1490
1515
1491
1516
(defun clojure-ts--defun-node-p (node )
1492
1517
" 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)))
1505
1528
1506
1529
(defconst clojure-ts--markdown-inline-sexp-nodes
1507
1530
'(" inline_link" " full_reference_link" " collapsed_reference_link"
@@ -1727,19 +1750,23 @@ Forms between BEG and END are aligned according to
1727
1750
1728
1751
; ;; Refactoring
1729
1752
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
+
1730
1767
(defun clojure-ts--threading-sexp-node ()
1731
1768
" 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 ))
1743
1770
1744
1771
(defun clojure-ts--delete-and-extract-sexp ()
1745
1772
" Delete the surrounding sexp and return it."
@@ -1874,9 +1901,7 @@ With universal argument \\[universal-argument], fully unwinds thread."
1874
1901
(n)
1875
1902
(1 )))
1876
1903
(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 )))
1880
1905
(save-excursion
1881
1906
(let ((beg (thread-first threading-sexp
1882
1907
(treesit-node-start)
@@ -1962,9 +1987,7 @@ cannot be found."
1962
1987
(interactive " p" )
1963
1988
(if-let* ((threading-sexp (clojure-ts--threading-sexp-node))
1964
1989
((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 )))
1968
1991
(let ((beg (thread-first threading-sexp
1969
1992
(treesit-node-start)
1970
1993
(copy-marker )))
@@ -2032,6 +2055,135 @@ value is `clojure-ts-thread-all-but-last'."
2032
2055
" -" ))))
2033
2056
(user-error " No defun at point" )))
2034
2057
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
+
2035
2187
(defun clojure-ts-cycle-keyword-string ()
2036
2188
" Convert the string at point to a keyword, or vice versa."
2037
2189
(interactive )
@@ -2141,6 +2293,8 @@ before DELIM-OPEN."
2141
2293
(keymap-set map " [" #'clojure-ts-convert-collection-to-vector )
2142
2294
(keymap-set map " C-#" #'clojure-ts-convert-collection-to-set )
2143
2295
(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 )
2144
2298
map)
2145
2299
" Keymap for `clojure-ts-mode' refactoring commands." )
2146
2300
@@ -2155,6 +2309,7 @@ before DELIM-OPEN."
2155
2309
[" Toggle between string & keyword" clojure-ts-cycle-keyword-string]
2156
2310
[" Align expression" clojure-ts-align]
2157
2311
[" Cycle privacy" clojure-ts-cycle-privacy]
2312
+ [" Add function/macro arity" clojure-ts-add-arity]
2158
2313
(" Convert collection"
2159
2314
[" Convert to list" clojure-ts-convert-collection-to-list]
2160
2315
[" Convert to quoted list" clojure-ts-convert-collection-to-quoted-list]
0 commit comments