@@ -1872,6 +1872,31 @@ functional literal node."
1872
1872
(clojure-ts--skip-first-child threading-sexp)
1873
1873
(not (treesit-end-of-thing 'sexp 2 'restricted )))))
1874
1874
1875
+ (defun clojure-ts--raise-sexp ()
1876
+ " Raise current sexp one level higher up the tree.
1877
+
1878
+ The built-in `raise-sexp' function doesn't work well with a few Clojure
1879
+ nodes (function literals, expressions with metadata etc.), it loses some
1880
+ parenthesis."
1881
+ (when-let* ((sexp-node (treesit-thing-at (point ) 'sexp ))
1882
+ (beg (thread-first sexp-node
1883
+ (clojure-ts--node-start-skip-metadata)
1884
+ (copy-marker )))
1885
+ (end (thread-first sexp-node
1886
+ (treesit-node-end)
1887
+ (copy-marker ))))
1888
+ (when-let* ((parent (treesit-node-parent sexp-node))
1889
+ ((not (string= (treesit-node-type parent) " source" )))
1890
+ (parent-beg (thread-first parent
1891
+ (clojure-ts--node-start-skip-metadata)
1892
+ (copy-marker )))
1893
+ (parent-end (thread-first parent
1894
+ (treesit-node-end)
1895
+ (copy-marker ))))
1896
+ (save-excursion
1897
+ (delete-region parent-beg beg)
1898
+ (delete-region end parent-end)))))
1899
+
1875
1900
(defun clojure-ts--pop-out-of-threading ()
1876
1901
" Raise a sexp up a level to unwind a threading form."
1877
1902
(let* ((threading-sexp (clojure-ts--threading-sexp-node))
@@ -2284,6 +2309,66 @@ before DELIM-OPEN."
2284
2309
(interactive )
2285
2310
(clojure-ts--convert-collection ?{ ?# ))
2286
2311
2312
+ (defun clojure-ts-cycle-conditional ()
2313
+ " Change a surrounding conditional form to its negated counterpart, or vice versa."
2314
+ (interactive )
2315
+ (if-let* ((sym-regex (rx bol
2316
+ (or " if" " if-not" " when" " when-not" )
2317
+ eol))
2318
+ (cond-node (clojure-ts--search-list-form-at-point sym-regex t ))
2319
+ (cond-sym (clojure-ts--list-node-sym-text cond-node)))
2320
+ (let ((beg (treesit-node-start cond-node))
2321
+ (end-marker (copy-marker (treesit-node-end cond-node)))
2322
+ (new-sym (pcase cond-sym
2323
+ (" if" " if-not" )
2324
+ (" if-not" " if" )
2325
+ (" when" " when-not" )
2326
+ (" when-not" " when" ))))
2327
+ (save-excursion
2328
+ (goto-char (clojure-ts--node-start-skip-metadata cond-node))
2329
+ (down-list 1 )
2330
+ (delete-char (length cond-sym))
2331
+ (insert new-sym)
2332
+ (when (member cond-sym '(" if" " if-not" ))
2333
+ (forward-sexp 2 )
2334
+ (transpose-sexps 1 ))
2335
+ (indent-region beg end-marker)))
2336
+ (user-error " No conditional expression found" )))
2337
+
2338
+ (defun clojure-ts--point-outside-node-p (node )
2339
+ " Return non-nil if point is outside of the actual NODE start.
2340
+
2341
+ Clojure grammar treats metadata as part of an expression, so for example
2342
+ ^boolean (not (= 2 2)) is a single list node, including metadata. This
2343
+ causes issues for functions that navigate by s-expressions and lists.
2344
+ This function returns non-nil if point is outside of the outermost
2345
+ parenthesis."
2346
+ (let* ((actual-node-start (clojure-ts--node-start-skip-metadata node))
2347
+ (node-end (treesit-node-end node))
2348
+ (pos (point )))
2349
+ (or (< pos actual-node-start)
2350
+ (> pos node-end))))
2351
+
2352
+ (defun clojure-ts-cycle-not ()
2353
+ " Add or remove a not form around the current form."
2354
+ (interactive )
2355
+ (if-let* ((list-node (clojure-ts--parent-until (rx bol " list_lit" eol)))
2356
+ ((not (clojure-ts--point-outside-node-p list-node))))
2357
+ (let ((beg (treesit-node-start list-node))
2358
+ (end-marker (copy-marker (treesit-node-end list-node)))
2359
+ (pos (copy-marker (point ) t )))
2360
+ (goto-char (clojure-ts--node-start-skip-metadata list-node))
2361
+ (if-let* ((list-parent (treesit-node-parent list-node))
2362
+ ((clojure-ts--list-node-sym-match-p list-parent (rx bol " not" eol))))
2363
+ (clojure-ts--raise-sexp)
2364
+ (insert-pair 1 ?\( ?\) )
2365
+ (insert " not " ))
2366
+ (indent-region beg end-marker)
2367
+ ; ; `save-excursion' doesn't work well when point is at the opening
2368
+ ; ; paren.
2369
+ (goto-char pos))
2370
+ (user-error " Must be invoked inside a list" )))
2371
+
2287
2372
(defvar clojure-ts-refactor-map
2288
2373
(let ((map (make-sparse-keymap )))
2289
2374
(keymap-set map " C-t" #'clojure-ts-thread )
@@ -2306,6 +2391,10 @@ before DELIM-OPEN."
2306
2391
(keymap-set map " [" #'clojure-ts-convert-collection-to-vector )
2307
2392
(keymap-set map " C-#" #'clojure-ts-convert-collection-to-set )
2308
2393
(keymap-set map " #" #'clojure-ts-convert-collection-to-set )
2394
+ (keymap-set map " C-c" #'clojure-ts-cycle-conditional )
2395
+ (keymap-set map " c" #'clojure-ts-cycle-conditional )
2396
+ (keymap-set map " C-o" #'clojure-ts-cycle-not )
2397
+ (keymap-set map " o" #'clojure-ts-cycle-not )
2309
2398
(keymap-set map " C-a" #'clojure-ts-add-arity )
2310
2399
(keymap-set map " a" #'clojure-ts-add-arity )
2311
2400
map)
@@ -2322,6 +2411,8 @@ before DELIM-OPEN."
2322
2411
[" Toggle between string & keyword" clojure-ts-cycle-keyword-string]
2323
2412
[" Align expression" clojure-ts-align]
2324
2413
[" Cycle privacy" clojure-ts-cycle-privacy]
2414
+ [" Cycle conditional" clojure-ts-cycle-conditional]
2415
+ [" Cycle not" clojure-ts-cycle-not]
2325
2416
[" Add function/macro arity" clojure-ts-add-arity]
2326
2417
(" Convert collection"
2327
2418
[" Convert to list" clojure-ts-convert-collection-to-list]
0 commit comments