@@ -1872,6 +1872,31 @@ functional literal node."
18721872 (clojure-ts--skip-first-child threading-sexp)
18731873 (not (treesit-end-of-thing 'sexp 2 'restricted )))))
18741874
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+
18751900(defun clojure-ts--pop-out-of-threading ()
18761901 " Raise a sexp up a level to unwind a threading form."
18771902 (let* ((threading-sexp (clojure-ts--threading-sexp-node))
@@ -2284,6 +2309,66 @@ before DELIM-OPEN."
22842309 (interactive )
22852310 (clojure-ts--convert-collection ?{ ?# ))
22862311
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+
22872372(defvar clojure-ts-refactor-map
22882373 (let ((map (make-sparse-keymap )))
22892374 (keymap-set map " C-t" #'clojure-ts-thread )
@@ -2306,6 +2391,10 @@ before DELIM-OPEN."
23062391 (keymap-set map " [" #'clojure-ts-convert-collection-to-vector )
23072392 (keymap-set map " C-#" #'clojure-ts-convert-collection-to-set )
23082393 (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 )
23092398 (keymap-set map " C-a" #'clojure-ts-add-arity )
23102399 (keymap-set map " a" #'clojure-ts-add-arity )
23112400 map)
@@ -2322,6 +2411,8 @@ before DELIM-OPEN."
23222411 [" Toggle between string & keyword" clojure-ts-cycle-keyword-string]
23232412 [" Align expression" clojure-ts-align]
23242413 [" Cycle privacy" clojure-ts-cycle-privacy]
2414+ [" Cycle conditional" clojure-ts-cycle-conditional]
2415+ [" Cycle not" clojure-ts-cycle-not]
23252416 [" Add function/macro arity" clojure-ts-add-arity]
23262417 (" Convert collection"
23272418 [" Convert to list" clojure-ts-convert-collection-to-list]
0 commit comments